Merge pull request #12308 from nextcloud/refactor/use-work-manager-file-download

Use Work Manager For File Download
This commit is contained in:
Alper Öztürk 2024-01-12 10:11:42 +01:00 committed by GitHub
commit ed88a4764c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 1592 additions and 1166 deletions

View file

@ -22,6 +22,7 @@ package com.nextcloud.client.files.downloader
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.rule.ServiceTestRule
import com.nextcloud.client.account.MockUser
import com.nextcloud.client.files.transfer.FileTransferService
import io.mockk.MockKAnnotations
import org.junit.Assert.assertTrue
import org.junit.Before

View file

@ -20,6 +20,11 @@
package com.nextcloud.client.files.downloader
import com.nextcloud.client.account.User
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Registry
import com.nextcloud.client.files.Request
import com.nextcloud.client.files.transfer.Transfer
import com.nextcloud.client.files.transfer.TransferState
import com.owncloud.android.datamodel.OCFile
import io.mockk.CapturingSlot
import io.mockk.MockKAnnotations

View file

@ -22,6 +22,12 @@ package com.nextcloud.client.files.downloader
import android.content.ComponentName
import android.content.Context
import com.nextcloud.client.account.MockUser
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.transfer.FileTransferService
import com.nextcloud.client.files.transfer.Transfer
import com.nextcloud.client.files.transfer.TransferManager
import com.nextcloud.client.files.transfer.TransferManagerConnection
import com.nextcloud.client.files.transfer.TransferState
import com.owncloud.android.datamodel.OCFile
import io.mockk.MockKAnnotations
import io.mockk.every

View file

@ -23,6 +23,12 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.nextcloud.client.account.User
import com.nextcloud.client.core.ManualAsyncRunner
import com.nextcloud.client.core.OnProgressCallback
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Request
import com.nextcloud.client.files.transfer.Transfer
import com.nextcloud.client.files.transfer.TransferManagerImpl
import com.nextcloud.client.files.transfer.TransferState
import com.nextcloud.client.files.upload.UploadTask
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import io.mockk.MockKAnnotations

View file

@ -23,6 +23,7 @@ package com.owncloud.android.files
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FileDownloadWorker
import com.nextcloud.test.TestActivity
import com.nextcloud.utils.EditorUtils
import com.owncloud.android.AbstractIT
@ -30,7 +31,6 @@ import com.owncloud.android.R
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.files.services.FileUploader
import com.owncloud.android.lib.resources.files.model.FileLockType
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
@ -62,7 +62,7 @@ class FileMenuFilterIT : AbstractIT() {
private lateinit var mockFileUploaderBinder: FileUploader.FileUploaderBinder
@MockK
private lateinit var mockFileDownloaderBinder: FileDownloader.FileDownloaderBinder
private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener
@MockK
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder
@ -77,8 +77,8 @@ class FileMenuFilterIT : AbstractIT() {
MockKAnnotations.init(this)
every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
every { mockComponentsGetter.fileUploaderBinder } returns mockFileUploaderBinder
every { mockFileDownloaderBinder.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloaderBinder } returns mockFileDownloaderBinder
every { mockFileDownloadProgressListener.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloadProgressListener } returns mockFileDownloadProgressListener
every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
every { mockStorageManager.getFileById(any()) } returns OCFile("/")

View file

@ -22,7 +22,6 @@ package com.owncloud.android.ui.activity;
*/
import android.content.Intent;
import android.view.View;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.R;

View file

@ -25,6 +25,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.nextcloud.client.files.downloader.FileDownloadWorker
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.utils.EditorUtils
@ -33,7 +34,6 @@ import com.owncloud.android.databinding.TestLayoutBinding
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.files.services.FileUploader
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.lib.resources.status.OwnCloudVersion
@ -130,7 +130,7 @@ class TestActivity :
return null
}
override fun getFileDownloaderBinder(): FileDownloader.FileDownloaderBinder? {
override fun getFileDownloadProgressListener(): FileDownloadWorker.FileDownloadProgressListener? {
return null
}

View file

@ -239,6 +239,12 @@
android:exported="false"
android:configChanges="orientation|screenLayout|screenSize|keyboardHidden"
android:theme="@style/Theme.ownCloud.Media" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:directBootAware="false"
android:enabled="@bool/enable_system_foreground_service_default"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".authentication.AccountAuthenticatorService"
android:exported="false">
@ -394,11 +400,7 @@
android:name=".services.OperationsService"
android:exported="false" />
<service
android:name=".files.services.FileDownloader"
android:foregroundServiceType="dataSync"
android:exported="false" />
<service
android:name="com.nextcloud.client.files.downloader.FileTransferService"
android:name="com.nextcloud.client.files.transfer.FileTransferService"
android:foregroundServiceType="dataSync"
android:exported="false" />
<service

View file

@ -26,6 +26,7 @@ import com.nextcloud.appReview.InAppReviewModule;
import com.nextcloud.client.appinfo.AppInfoModule;
import com.nextcloud.client.database.DatabaseModule;
import com.nextcloud.client.device.DeviceModule;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.integrations.IntegrationsModule;
import com.nextcloud.client.jobs.JobsModule;
import com.nextcloud.client.network.NetworkModule;
@ -71,6 +72,8 @@ public interface AppComponent {
void inject(FilesUploadHelper filesUploadHelper);
void inject(FileDownloadHelper fileDownloadHelper);
void inject(ProgressIndicator progressIndicator);
@Component.Builder

View file

@ -24,7 +24,7 @@ import com.nextcloud.client.documentscan.DocumentScanActivity;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.etm.EtmActivity;
import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment;
import com.nextcloud.client.files.downloader.FileTransferService;
import com.nextcloud.client.files.transfer.FileTransferService;
import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
import com.nextcloud.client.jobs.NotificationWork;
import com.nextcloud.client.jobs.TestJob;
@ -46,7 +46,6 @@ import com.owncloud.android.MainApp;
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.authentication.DeepLinkLoginActivity;
import com.owncloud.android.files.BootupBroadcastReceiver;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.providers.DiskLruImageCacheFileProvider;
import com.owncloud.android.providers.DocumentsStorageProvider;
@ -324,9 +323,6 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract FileUploader fileUploader();
@ContributesAndroidInjector
abstract FileDownloader fileDownloader();
@ContributesAndroidInjector
abstract BootupBroadcastReceiver bootupBroadcastReceiver();

View file

@ -35,7 +35,7 @@ import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment
import com.nextcloud.client.etm.pages.EtmFileTransferFragment
import com.nextcloud.client.etm.pages.EtmMigrations
import com.nextcloud.client.etm.pages.EtmPreferencesFragment
import com.nextcloud.client.files.downloader.TransferManagerConnection
import com.nextcloud.client.files.transfer.TransferManagerConnection
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.JobInfo
import com.nextcloud.client.migrations.MigrationInfo

View file

@ -13,10 +13,10 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.client.etm.EtmBaseFragment
import com.nextcloud.client.files.downloader.DownloadRequest
import com.nextcloud.client.files.downloader.Transfer
import com.nextcloud.client.files.downloader.TransferManager
import com.nextcloud.client.files.downloader.UploadRequest
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.UploadRequest
import com.nextcloud.client.files.transfer.Transfer
import com.nextcloud.client.files.transfer.TransferManager
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.db.OCUpload

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files
enum class Direction {
DOWNLOAD,

View file

@ -1,8 +1,9 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,10 +16,12 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files
import com.nextcloud.client.files.transfer.Transfer
import com.nextcloud.client.files.transfer.TransferState
import com.owncloud.android.datamodel.OCFile
import java.util.UUID
import kotlin.math.max

View file

@ -1,8 +1,9 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,13 +16,15 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files
import android.os.Parcel
import android.os.Parcelable
import com.nextcloud.client.account.User
import com.nextcloud.client.files.upload.PostUploadAction
import com.nextcloud.client.files.upload.UploadTrigger
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.db.OCUpload

View file

@ -0,0 +1,159 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
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))
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

@ -22,6 +22,7 @@ package com.nextcloud.client.files.downloader
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
@ -37,9 +38,9 @@ import java.io.File
* This design can be regarded as intermediary refactoring step.
*/
class DownloadTask(
val context: Context,
val contentResolver: ContentResolver,
val clientProvider: () -> OwnCloudClient
private val context: Context,
private val contentResolver: ContentResolver,
private val clientProvider: () -> OwnCloudClient
) {
data class Result(val file: OCFile, val success: Boolean)
@ -62,39 +63,47 @@ class DownloadTask(
}
}
// 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)
if (result.isSuccess) {
return if (result.isSuccess) {
val storageManager = FileDataStorageManager(
request.user,
contentResolver
)
val file = saveDownloadedFile(op, storageManager)
return Result(file, true)
Result(file, true)
} else {
return Result(request.file, false)
Result(request.file, false)
}
}
private fun saveDownloadedFile(op: DownloadFileOperation, storageManager: FileDataStorageManager): OCFile {
val file = storageManager.getFileById(op.getFile().getFileId()) as OCFile
val syncDate = System.currentTimeMillis()
file.lastSyncDateForProperties = syncDate
file.lastSyncDateForData = syncDate
file.isUpdateThumbnailNeeded = true
file.modificationTimestamp = op.getModificationTimestamp()
file.modificationTimestampAtLastSyncForData = op.getModificationTimestamp()
file.etag = op.getEtag()
file.mimeType = op.getMimeType()
file.storagePath = op.getSavePath()
file.fileLength = File(op.getSavePath()).length()
file.remoteId = op.getFile().getRemoteId()
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.getMimeType())) {
if (MimeTypeUtil.isMedia(op.mimeType)) {
FileDataStorageManager.triggerMediaScan(file.storagePath)
}
return file
}
}

View file

@ -0,0 +1,26 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
enum class FileDownloadError {
Failed, Cancelled
}

View file

@ -0,0 +1,162 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
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)
return if (file.isFolder) {
backgroundJobManager.isStartFileDownloadJobScheduled(user, file.fileId) ||
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,98 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
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,477 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
import android.accounts.Account
import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.app.PendingIntent
import android.content.Context
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.java.util.Optional
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.Vector
@Suppress("LongParameterList", "TooManyFunctions")
class FileDownloadWorker(
private val 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 WORKER_ID = "WORKER_ID"
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 lateinit var notificationManager: DownloadNotificationManager
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 workerId: Int? = null
private var downloadError: FileDownloadError? = null
@Suppress("TooGenericExceptionCaught")
override fun doWork(): Result {
return try {
val requestDownloads = getRequestDownloads()
notificationManager =
DownloadNotificationManager(workerId ?: SecureRandom().nextInt(), context, viewThemeUtils)
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> {
workerId = inputData.keyValueMap[WORKER_ID] as Int
Log_OC.e(TAG, "FilesDownloadWorker started for $workerId")
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
)
if (downloadKey != null) {
requestedDownloads.add(downloadKey)
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")
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)
}
}
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)
}
}
}
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
)
}
}
}

View file

@ -1,8 +1,9 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,9 +16,9 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.transfer
import android.app.Service
import android.content.Context
@ -27,6 +28,10 @@ import com.nextcloud.client.account.User
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.core.LocalBinder
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.files.Direction
import com.nextcloud.client.files.Request
import com.nextcloud.client.files.downloader.DownloadTask
import com.nextcloud.client.files.upload.UploadTask
import com.nextcloud.client.logger.Logger
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.network.ConnectivityService

View file

@ -1,8 +1,9 @@
/**
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,10 +16,14 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.transfer
import com.nextcloud.client.files.Direction
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Request
import com.nextcloud.client.files.UploadRequest
import com.owncloud.android.datamodel.OCFile
import java.util.UUID
@ -48,8 +53,9 @@ data class Transfer(
*/
val isFinished: Boolean get() = state == TransferState.COMPLETED || state == TransferState.FAILED
val direction: Direction get() = when (request) {
is DownloadRequest -> Direction.DOWNLOAD
is UploadRequest -> Direction.UPLOAD
}
val direction: Direction
get() = when (request) {
is DownloadRequest -> Direction.DOWNLOAD
is UploadRequest -> Direction.UPLOAD
}
}

View file

@ -1,8 +1,9 @@
/**
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,10 +16,11 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.transfer
import com.nextcloud.client.files.Request
import com.owncloud.android.datamodel.OCFile
import java.util.UUID

View file

@ -1,8 +1,9 @@
/**
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,15 +16,16 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.transfer
import android.content.Context
import android.content.Intent
import android.os.IBinder
import com.nextcloud.client.account.User
import com.nextcloud.client.core.LocalConnection
import com.nextcloud.client.files.Request
import com.owncloud.android.datamodel.OCFile
import java.util.UUID

View file

@ -1,8 +1,9 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,14 +16,20 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.transfer
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.core.IsCancelled
import com.nextcloud.client.core.OnProgressCallback
import com.nextcloud.client.core.TaskFunction
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Registry
import com.nextcloud.client.files.Request
import com.nextcloud.client.files.UploadRequest
import com.nextcloud.client.files.downloader.DownloadTask
import com.nextcloud.client.files.upload.UploadTask
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.operations.UploadFileOperation
import java.util.UUID
@ -150,7 +157,7 @@ class TransferManagerImpl(
}
} else {
val uploadTask = uploadTaskFactory.create()
val wrapper: TaskFunction<UploadTask.Result, Int> = { progress: ((Int) -> Unit), isCancelled ->
val wrapper: TaskFunction<UploadTask.Result, Int> = { _: ((Int) -> Unit), _ ->
uploadTask.upload(request.user, request.upload)
}
wrapper

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.transfer
enum class TransferState {
PENDING,

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.upload
import com.owncloud.android.files.services.FileUploader

View file

@ -1,8 +1,9 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,9 +16,9 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.upload
import android.content.Context
import com.nextcloud.client.account.User

View file

@ -1,8 +1,9 @@
/*
* Nextcloud Android client application
*
* @author Chris Narkiewicz
* Copyright (C) 2021 Chris Narkiewicz <hello@ezaquarii.com>
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -15,9 +16,9 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.files.downloader
package com.nextcloud.client.files.upload
import com.owncloud.android.operations.UploadFileOperation

View file

@ -34,6 +34,7 @@ import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.documentscan.GeneratePDFUseCase
import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
import com.nextcloud.client.files.downloader.FileDownloadWorker
import com.nextcloud.client.integrations.deck.DeckApi
import com.nextcloud.client.logger.Logger
import com.nextcloud.client.network.ConnectivityService
@ -102,6 +103,7 @@ class BackgroundJobFactory @Inject constructor(
CalendarImportWork::class -> createCalendarImportWork(context, workerParameters)
FilesExportWork::class -> createFilesExportWork(context, workerParameters)
FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
FileDownloadWorker::class -> createFilesDownloadWorker(context, workerParameters)
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
TestJob::class -> createTestJob(context, workerParameters)
@ -253,6 +255,16 @@ class BackgroundJobFactory @Inject constructor(
)
}
private fun createFilesDownloadWorker(context: Context, params: WorkerParameters): FileDownloadWorker {
return FileDownloadWorker(
viewThemeUtils.get(),
accountManager,
localBroadcastManager.get(),
context,
params
)
}
private fun createPDFGenerateWork(context: Context, params: WorkerParameters): GeneratePdfFromImagesWork {
return GeneratePdfFromImagesWork(
appContext = context,

View file

@ -23,6 +23,7 @@ import androidx.lifecycle.LiveData
import androidx.work.ListenableWorker
import com.nextcloud.client.account.User
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.operations.DownloadType
/**
* This interface allows to control, schedule and monitor all application
@ -144,6 +145,21 @@ interface BackgroundJobManager {
fun getFileUploads(user: User): LiveData<List<JobInfo>>
fun cancelFilesUploadJob(user: User)
fun cancelFilesDownloadJob(user: User, fileId: Long)
fun isStartFileDownloadJobScheduled(user: User, fileId: Long): Boolean
@Suppress("LongParameterList")
fun startFileDownloadJob(
user: User,
file: OCFile,
behaviour: String,
downloadType: DownloadType?,
activityName: String,
packageName: String,
conflictUploadId: Long?
)
fun startPdfGenerateAndUploadWork(user: User, uploadFolder: String, imagePaths: List<String>, pdfPath: String)
fun scheduleTestJob()

View file

@ -38,8 +38,11 @@ import com.nextcloud.client.account.User
import com.nextcloud.client.core.Clock
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
import com.nextcloud.client.files.downloader.FileDownloadWorker
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.extensions.isWorkScheduled
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.operations.DownloadType
import java.util.Date
import java.util.UUID
import java.util.concurrent.TimeUnit
@ -83,6 +86,8 @@ internal class BackgroundJobManagerImpl(
const val JOB_NOTIFICATION = "notification"
const val JOB_ACCOUNT_REMOVAL = "account_removal"
const val JOB_FILES_UPLOAD = "files_upload"
const val JOB_FOLDER_DOWNLOAD = "folder_download"
const val JOB_FILES_DOWNLOAD = "files_download"
const val JOB_PDF_GENERATION = "pdf_generation"
const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup"
const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export"
@ -494,7 +499,6 @@ internal class BackgroundJobManagerImpl(
workManager.enqueue(request)
}
override fun startFilesUploadJob(user: User) {
val data = workDataOf(FilesUploadWorker.ACCOUNT to user.accountName)
@ -505,6 +509,44 @@ internal class BackgroundJobManagerImpl(
workManager.enqueueUniqueWork(JOB_FILES_UPLOAD + user.accountName, ExistingWorkPolicy.KEEP, request)
}
private fun startFileDownloadJobTag(user: User, fileId: Long): String {
return JOB_FOLDER_DOWNLOAD + user.accountName + fileId
}
override fun isStartFileDownloadJobScheduled(user: User, fileId: Long): Boolean {
return workManager.isWorkScheduled(startFileDownloadJobTag(user, fileId))
}
override fun startFileDownloadJob(
user: User,
file: OCFile,
behaviour: String,
downloadType: DownloadType?,
activityName: String,
packageName: String,
conflictUploadId: Long?
) {
val tag = startFileDownloadJobTag(user, file.fileId)
val data = workDataOf(
FileDownloadWorker.WORKER_ID to file.fileId.toInt(),
FileDownloadWorker.ACCOUNT_NAME to user.accountName,
FileDownloadWorker.FILE_REMOTE_PATH to file.remotePath,
FileDownloadWorker.BEHAVIOUR to behaviour,
FileDownloadWorker.DOWNLOAD_TYPE to downloadType.toString(),
FileDownloadWorker.ACTIVITY_NAME to activityName,
FileDownloadWorker.PACKAGE_NAME to packageName,
FileDownloadWorker.CONFLICT_UPLOAD_ID to conflictUploadId
)
val request = oneTimeRequestBuilder(FileDownloadWorker::class, JOB_FILES_DOWNLOAD, user)
.addTag(tag)
.setInputData(data)
.build()
workManager.enqueueUniqueWork(tag, ExistingWorkPolicy.REPLACE, request)
}
override fun getFileUploads(user: User): LiveData<List<JobInfo>> {
val workInfo = workManager.getWorkInfosByTagLiveData(formatNameTag(JOB_FILES_UPLOAD, user))
return workInfo.map { it -> it.map { fromWorkInfo(it) ?: JobInfo() } }
@ -514,6 +556,10 @@ internal class BackgroundJobManagerImpl(
workManager.cancelJob(JOB_FILES_UPLOAD, user)
}
override fun cancelFilesDownloadJob(user: User, fileId: Long) {
workManager.cancelAllWorkByTag(startFileDownloadJobTag(user, fileId))
}
override fun startPdfGenerateAndUploadWork(
user: User,
uploadFolder: String,

View file

@ -35,10 +35,10 @@ import androidx.work.Worker
import androidx.work.WorkerParameters
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.files.downloader.PostUploadAction
import com.nextcloud.client.files.downloader.TransferManagerConnection
import com.nextcloud.client.files.downloader.UploadRequest
import com.nextcloud.client.files.downloader.UploadTrigger
import com.nextcloud.client.files.UploadRequest
import com.nextcloud.client.files.transfer.TransferManagerConnection
import com.nextcloud.client.files.upload.PostUploadAction
import com.nextcloud.client.files.upload.UploadTrigger
import com.owncloud.android.R
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.FileDataStorageManager

View file

@ -33,13 +33,12 @@ import androidx.core.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FileDownloadHelper
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.DownloadType
import com.owncloud.android.ui.dialog.SendShareDialog
import com.owncloud.android.ui.notifications.NotificationUtils
import com.owncloud.android.utils.FileExportUtils
import com.owncloud.android.utils.FileStorageUtils
@ -112,13 +111,11 @@ class FilesExportWork(
}
private fun downloadFile(ocFile: OCFile) {
val i = Intent(appContext, FileDownloader::class.java)
i.putExtra(FileDownloader.EXTRA_USER, user)
i.putExtra(FileDownloader.EXTRA_FILE, ocFile)
i.putExtra(SendShareDialog.PACKAGE_NAME, "")
i.putExtra(SendShareDialog.ACTIVITY_NAME, "")
i.putExtra(FileDownloader.DOWNLOAD_TYPE, DownloadType.EXPORT)
appContext.startService(i)
FileDownloadHelper.instance().downloadFile(
user,
ocFile,
downloadType = DownloadType.EXPORT
)
}
private fun showErrorNotification(successfulExports: Int) {

View file

@ -45,7 +45,7 @@ class AppNotificationManagerImpl @Inject constructor(
val icon = BitmapFactory.decodeResource(resources, R.drawable.notification_icon)
return builder(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
.setContentTitle(resources.getString(R.string.app_name))
.setContentText(resources.getString(R.string.foreground_service_download))
.setContentText(resources.getString(R.string.worker_download))
.setSmallIcon(R.drawable.notification_icon)
.setLargeIcon(icon)
.build()

View file

@ -0,0 +1,30 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.model
import com.nextcloud.client.account.User
import com.owncloud.android.operations.DownloadFileOperation
sealed class WorkerState {
object Idle : WorkerState()
class Download(var user: User?, var currentDownload: DownloadFileOperation?) : WorkerState()
}

View file

@ -0,0 +1,41 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.model
import androidx.lifecycle.LiveData
class WorkerStateLiveData private constructor() : LiveData<WorkerState>() {
fun setWorkState(state: WorkerState) {
postValue(state)
}
companion object {
private var instance: WorkerStateLiveData? = null
fun instance(): WorkerStateLiveData {
return instance ?: synchronized(this) {
instance ?: WorkerStateLiveData().also { instance = it }
}
}
}
}

View file

@ -25,6 +25,7 @@ import android.app.Notification
import android.app.Service
import android.os.Build
import androidx.core.app.ServiceCompat
import androidx.work.ForegroundInfo
import com.owncloud.android.datamodel.ForegroundServiceType
object ForegroundServiceHelper {
@ -45,4 +46,16 @@ object ForegroundServiceHelper {
service.startForeground(id, notification)
}
}
fun createWorkerForegroundInfo(
id: Int,
notification: Notification,
foregroundServiceType: ForegroundServiceType
): ForegroundInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ForegroundInfo(id, notification, foregroundServiceType.getId())
} else {
ForegroundInfo(id, notification)
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.utils.extensions
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.google.common.util.concurrent.ListenableFuture
import com.owncloud.android.lib.common.utils.Log_OC
import java.util.concurrent.ExecutionException
fun WorkManager.isWorkScheduled(tag: String): Boolean {
val statuses: ListenableFuture<List<WorkInfo>> = this.getWorkInfosByTag(tag)
var running = false
var workInfoList: List<WorkInfo> = emptyList()
try {
workInfoList = statuses.get()
} catch (e: ExecutionException) {
Log_OC.d("Worker", "ExecutionException in isWorkScheduled: $e")
} catch (e: InterruptedException) {
Log_OC.d("Worker", "InterruptedException in isWorkScheduled: $e")
}
for (workInfo in workInfoList) {
val state = workInfo.state
running = running || (state == WorkInfo.State.RUNNING || state == WorkInfo.State.ENQUEUED)
}
return running
}

View file

@ -180,6 +180,50 @@ public class FileDataStorageManager {
return fileDao.getFileByEncryptedRemotePath(path, user.getAccountName()) != null;
}
public long getTopParentId(OCFile file) {
if (file.getParentId() == 1) {
return file.getFileId();
}
return getTopParentIdRecursive(file);
}
private long getTopParentIdRecursive(OCFile file) {
if (file.getParentId() == 1) {
return file.getFileId();
}
OCFile parentFile = getFileById(file.getParentId());
if (parentFile != null) {
return getTopParentId(parentFile);
}
return file.getFileId();
}
public List<OCFile> getAllFilesRecursivelyInsideFolder(OCFile file) {
ArrayList<OCFile> result = new ArrayList<>();
if (file == null || !file.fileExists()) {
return result;
}
if (!file.isFolder()) {
result.add(file);
return result;
}
List<OCFile> filesInsideFolder = getFolderContent(file.getFileId(), false);
for (OCFile item: filesInsideFolder) {
if (!item.isFolder()) {
result.add(item);
} else {
result.addAll(getAllFilesRecursivelyInsideFolder(item));
}
}
return result;
}
public List<OCFile> getFolderContent(OCFile ocFile, boolean onlyOnDevice) {
if (ocFile != null && ocFile.isFolder() && ocFile.fileExists()) {
@ -189,7 +233,6 @@ public class FileDataStorageManager {
}
}
public List<OCFile> getFolderImages(OCFile folder, boolean onlyOnDevice) {
List<OCFile> imageList = new ArrayList<>();

View file

@ -31,11 +31,11 @@ import android.view.Menu;
import com.nextcloud.android.files.FileLockingHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.utils.EditorUtils;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
@ -380,9 +380,8 @@ public class FileMenuFilter {
if (componentsGetter != null && !files.isEmpty() && user != null) {
OperationsServiceBinder opsBinder = componentsGetter.getOperationsServiceBinder();
FileUploaderBinder uploaderBinder = componentsGetter.getFileUploaderBinder();
FileDownloaderBinder downloaderBinder = componentsGetter.getFileDownloaderBinder();
synchronizing = anyFileSynchronizing(opsBinder) || // comparing local and remote
anyFileDownloading(downloaderBinder) ||
anyFileDownloading() ||
anyFileUploading(uploaderBinder);
}
return synchronizing;
@ -398,14 +397,14 @@ public class FileMenuFilter {
return synchronizing;
}
private boolean anyFileDownloading(FileDownloaderBinder downloaderBinder) {
boolean downloading = false;
if (downloaderBinder != null) {
for (Iterator<OCFile> iterator = files.iterator(); !downloading && iterator.hasNext(); ) {
downloading = downloaderBinder.isDownloading(user, iterator.next());
private boolean anyFileDownloading() {
for (OCFile file : files) {
if (FileDownloadHelper.Companion.instance().isDownloading(user, file)) {
return true;
}
}
return downloading;
return false;
}
private boolean anyFileUploading(FileUploaderBinder uploaderBinder) {

View file

@ -1,744 +0,0 @@
/*
* ownCloud Android client application
*
* Copyright (C) 2012 Bartek Przybylski
* Copyright (C) 2012-2016 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* 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 com.owncloud.android.files.services;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Pair;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.files.downloader.DownloadTask;
import com.nextcloud.java.util.Optional;
import com.nextcloud.utils.ForegroundServiceHelper;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.ForegroundServiceType;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
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.lib.resources.files.FileUtils;
import com.owncloud.android.operations.DownloadFileOperation;
import com.owncloud.android.operations.DownloadType;
import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.activity.ConflictsResolveActivity;
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.notifications.NotificationUtils;
import com.owncloud.android.ui.preview.PreviewImageActivity;
import com.owncloud.android.ui.preview.PreviewImageFragment;
import com.owncloud.android.utils.ErrorMessageAdapter;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
import java.security.SecureRandom;
import java.util.AbstractList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import javax.inject.Inject;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import dagger.android.AndroidInjection;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
public class FileDownloader extends Service
implements OnDatatransferProgressListener, OnAccountsUpdateListener {
public static final String EXTRA_USER = "USER";
public static final String EXTRA_FILE = "FILE";
private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
public static final String DOWNLOAD_TYPE = "DOWNLOAD_TYPE";
private static final int FOREGROUND_SERVICE_ID = 412;
private static final String TAG = FileDownloader.class.getSimpleName();
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private IBinder mBinder;
private OwnCloudClient mDownloadClient;
private Optional<User> currentUser = Optional.empty();
private FileDataStorageManager mStorageManager;
private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<>();
private DownloadFileOperation mCurrentDownload;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;
private int mLastPercent;
private Notification mNotification;
private long conflictUploadId;
public boolean mStartedDownload = false;
@Inject UserAccountManager accountManager;
@Inject UploadsStorageManager uploadsStorageManager;
@Inject LocalBroadcastManager localBroadcastManager;
@Inject ViewThemeUtils viewThemeUtils;
public static String getDownloadAddedMessage() {
return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
}
public static String getDownloadFinishMessage() {
return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
}
/**
* Service initialization
*/
@Override
public void onCreate() {
super.onCreate();
AndroidInjection.inject(this);
Log_OC.d(TAG, "Creating service");
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
HandlerThread thread = new HandlerThread("FileDownloaderThread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper, this);
mBinder = new FileDownloaderBinder();
NotificationCompat.Builder builder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils).setContentTitle(
getApplicationContext().getResources().getString(R.string.app_name))
.setContentText(getApplicationContext().getResources().getString(R.string.foreground_service_download))
.setSmallIcon(R.drawable.notification_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.notification_icon));
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD);
}
mNotification = builder.build();
// add AccountsUpdatedListener
AccountManager am = AccountManager.get(getApplicationContext());
am.addOnAccountsUpdatedListener(this, null, false);
}
/**
* Service clean up
*/
@Override
public void onDestroy() {
Log_OC.v(TAG, "Destroying service");
mBinder = null;
mServiceHandler = null;
mServiceLooper.quit();
mServiceLooper = null;
mNotificationManager = null;
// remove AccountsUpdatedListener
AccountManager am = AccountManager.get(getApplicationContext());
am.removeOnAccountsUpdatedListener(this);
super.onDestroy();
}
/**
* Entry point to add one or several files to the queue of downloads.
* <p>
* New downloads are added calling to startService(), resulting in a call to this method. This ensures the service
* will keep on working although the caller activity goes away.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log_OC.d(TAG, "Starting command with id " + startId);
ForegroundServiceHelper.INSTANCE.startService(this, FOREGROUND_SERVICE_ID, mNotification, ForegroundServiceType.DataSync);
if (intent == null || !intent.hasExtra(EXTRA_USER) || !intent.hasExtra(EXTRA_FILE)) {
Log_OC.e(TAG, "Not enough information provided in intent");
return START_NOT_STICKY;
} else {
final User user = IntentExtensionsKt.getParcelableArgument(intent, EXTRA_USER, User.class);
final OCFile file = IntentExtensionsKt.getParcelableArgument(intent, EXTRA_FILE, OCFile.class);
final String behaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
DownloadType downloadType = DownloadType.DOWNLOAD;
if (intent.hasExtra(DOWNLOAD_TYPE)) {
downloadType = IntentExtensionsKt.getSerializableArgument(intent, DOWNLOAD_TYPE, DownloadType.class);
}
String activityName = intent.getStringExtra(SendShareDialog.ACTIVITY_NAME);
String packageName = intent.getStringExtra(SendShareDialog.PACKAGE_NAME);
conflictUploadId = intent.getLongExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, -1);
AbstractList<String> requestedDownloads = new Vector<String>();
try {
DownloadFileOperation newDownload = new DownloadFileOperation(user,
file,
behaviour,
activityName,
packageName,
getBaseContext(),
downloadType);
newDownload.addDatatransferProgressListener(this);
newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
Pair<String, String> putResult = mPendingDownloads.putIfAbsent(user.getAccountName(),
file.getRemotePath(),
newDownload);
if (putResult != null) {
String downloadKey = putResult.first;
requestedDownloads.add(downloadKey);
sendBroadcastNewDownload(newDownload, putResult.second);
} // else, file already in the queue of downloads; don't repeat the request
} catch (IllegalArgumentException e) {
Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
return START_NOT_STICKY;
}
if (requestedDownloads.size() > 0) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = requestedDownloads;
mServiceHandler.sendMessage(msg);
}
}
return START_NOT_STICKY;
}
/**
* Provides a binder object that clients can use to perform operations on the queue of downloads,
* excepting the addition of new files.
*
* Implemented to perform cancellation, pause and resume of existing downloads.
*/
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Called when ALL the bound clients were onbound.
*/
@Override
public boolean onUnbind(Intent intent) {
((FileDownloaderBinder) mBinder).clearListeners();
return false; // not accepting rebinding (default behaviour)
}
@Override
public void onAccountsUpdated(Account[] accounts) {
//review the current download and cancel it if its account doesn't exist
if (mCurrentDownload != null && !accountManager.exists(mCurrentDownload.getUser().toPlatformAccount())) {
mCurrentDownload.cancel();
}
// The rest of downloads are cancelled when they try to start
}
/**
* Binder to let client components to perform operations on the queue of downloads.
* <p/>
* It provides by itself the available operations.
*/
public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {
/**
* Map of listeners that will be reported about progress of downloads from a
* {@link FileDownloaderBinder}
* instance.
*/
private Map<Long, OnDatatransferProgressListener> mBoundListeners =
new HashMap<Long, OnDatatransferProgressListener>();
/**
* Cancels a pending or current download of a remote file.
*
* @param account ownCloud account where the remote file is stored.
* @param file A file in the queue of pending downloads
*/
public void cancel(Account account, OCFile file) {
Pair<DownloadFileOperation, String> removeResult =
mPendingDownloads.remove(account.name, file.getRemotePath());
DownloadFileOperation download = removeResult.first;
if (download != null) {
download.cancel();
} else {
if (mCurrentDownload != null && currentUser.isPresent() &&
mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
account.name.equals(currentUser.get().getAccountName())) {
mCurrentDownload.cancel();
}
}
}
/**
* Cancels all the downloads for an account
*/
public void cancel(String accountName) {
if (mCurrentDownload != null && mCurrentDownload.getUser().nameEquals(accountName)) {
mCurrentDownload.cancel();
}
// Cancel pending downloads
cancelPendingDownloads(accountName);
}
public void clearListeners() {
mBoundListeners.clear();
}
/**
* Returns True when the file described by 'file' in the ownCloud account 'account'
* is downloading or waiting to download.
*
* If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
* waiting to download.
*
* @param user user where the remote file is stored.
* @param file A file that could be in the queue of downloads.
*/
public boolean isDownloading(User user, OCFile file) {
return user != null && file != null && mPendingDownloads.contains(user.getAccountName(), file.getRemotePath());
}
/**
* Adds a listener interested in the progress of the download for a concrete file.
*
* @param listener Object to notify about progress of transfer.
* @param file {@link OCFile} of interest for listener.
*/
public void addDatatransferProgressListener(OnDatatransferProgressListener listener, OCFile file) {
if (file == null || listener == null) {
return;
}
mBoundListeners.put(file.getFileId(), listener);
}
/**
* Removes a listener interested in the progress of the download for a concrete file.
*
* @param listener Object to notify about progress of transfer.
* @param file {@link OCFile} of interest for listener.
*/
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener, OCFile file) {
if (file == null || listener == null) {
return;
}
Long fileId = file.getFileId();
if (mBoundListeners.get(fileId) == listener) {
mBoundListeners.remove(fileId);
}
}
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar,
long totalToTransfer, String fileName) {
OnDatatransferProgressListener boundListener =
mBoundListeners.get(mCurrentDownload.getFile().getFileId());
if (boundListener != null) {
boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
totalToTransfer, fileName);
}
}
}
/**
* Download worker. Performs the pending downloads in the order they were requested.
* Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
*/
private static class ServiceHandler extends Handler {
// don't make it a final class, and don't remove the static ; lint will warn about a
// possible memory leak
FileDownloader mService;
public ServiceHandler(Looper looper, FileDownloader service) {
super(looper);
if (service == null) {
throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
}
mService = service;
}
@Override
public void handleMessage(Message msg) {
@SuppressWarnings("unchecked")
AbstractList<String> requestedDownloads = (AbstractList<String>) msg.obj;
if (msg.obj != null) {
Iterator<String> it = requestedDownloads.iterator();
while (it.hasNext()) {
String next = it.next();
mService.downloadFile(next);
}
}
mService.mStartedDownload=false;
(new Handler()).postDelayed(() -> {
if(!mService.mStartedDownload){
mService.mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker);
}
Log_OC.d(TAG, "Stopping after command with id " + msg.arg1);
mService.mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
mService.stopForeground(true);
mService.stopSelf(msg.arg1);
}, 2000);
}
}
/**
* Core download method: requests a file to download and stores it.
*
* @param downloadKey Key to access the download to perform, contained in mPendingDownloads
*/
private void downloadFile(String downloadKey) {
mStartedDownload = true;
mCurrentDownload = mPendingDownloads.get(downloadKey);
if (mCurrentDownload != null) {
// Detect if the account exists
if (accountManager.exists(mCurrentDownload.getUser().toPlatformAccount())) {
notifyDownloadStart(mCurrentDownload);
RemoteOperationResult downloadResult = null;
try {
/// prepare client object to send the request to the ownCloud server
Account currentDownloadAccount = mCurrentDownload.getUser().toPlatformAccount();
Optional<User> currentDownloadUser = accountManager.getUser(currentDownloadAccount.name);
if (!currentUser.equals(currentDownloadUser)) {
currentUser = currentDownloadUser;
mStorageManager = new FileDataStorageManager(currentUser.get(), getContentResolver());
} // else, reuse storage manager from previous operation
// always get client from client manager, to get fresh credentials in case
// of update
OwnCloudAccount ocAccount = currentDownloadUser.get().toOwnCloudAccount();
mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
getClientFor(ocAccount, this);
/// perform the download
downloadResult = mCurrentDownload.execute(mDownloadClient);
if (downloadResult.isSuccess() && mCurrentDownload.getDownloadType() == DownloadType.DOWNLOAD) {
saveDownloadedFile();
}
} catch (Exception e) {
Log_OC.e(TAG, "Error downloading", e);
downloadResult = new RemoteOperationResult(e);
} finally {
Pair<DownloadFileOperation, String> removeResult = mPendingDownloads.removePayload(
mCurrentDownload.getUser().getAccountName(), mCurrentDownload.getRemotePath());
if (downloadResult == null) {
downloadResult = new RemoteOperationResult(new RuntimeException("Error downloading…"));
}
/// notify result
notifyDownloadResult(mCurrentDownload, downloadResult);
sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
}
} else {
cancelPendingDownloads(mCurrentDownload.getUser().getAccountName());
}
}
}
/**
* Updates the OC File after a successful download.
*
* TODO move to DownloadFileOperation
* unify with code from {@link DocumentsStorageProvider} and {@link DownloadTask}.
*/
private void saveDownloadedFile() {
OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
if (file == null) {
// try to get file via path, needed for overwriting existing files on conflict dialog
file = mStorageManager.getFileByDecryptedRemotePath(mCurrentDownload.getFile().getRemotePath());
}
if (file == null) {
Log_OC.e(this, "Could not save " + mCurrentDownload.getFile().getRemotePath());
return;
}
long syncDate = System.currentTimeMillis();
file.setLastSyncDateForProperties(syncDate);
file.setLastSyncDateForData(syncDate);
file.setUpdateThumbnailNeeded(true);
file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());
file.setEtag(mCurrentDownload.getEtag());
file.setMimeType(mCurrentDownload.getMimeType());
file.setStoragePath(mCurrentDownload.getSavePath());
file.setFileLength(new File(mCurrentDownload.getSavePath()).length());
file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
mStorageManager.saveFile(file);
if (MimeTypeUtil.isMedia(mCurrentDownload.getMimeType())) {
FileDataStorageManager.triggerMediaScan(file.getStoragePath(), file);
}
mStorageManager.saveConflict(file, null);
}
/**
* Creates a status notification to show the download progress
*
* @param download Download operation starting.
*/
private void notifyDownloadStart(DownloadFileOperation download) {
/// create status notification with a progress bar
mLastPercent = 0;
mNotificationBuilder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils);
mNotificationBuilder
.setSmallIcon(R.drawable.notification_icon)
.setTicker(getString(R.string.downloader_download_in_progress_ticker))
.setContentTitle(getString(R.string.downloader_download_in_progress_ticker))
.setOngoing(true)
.setProgress(100, 0, download.getSize() < 0)
.setContentText(
String.format(getString(R.string.downloader_download_in_progress_content), 0,
new File(download.getSavePath()).getName())
);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD);
}
/// includes a pending intent in the notification showing the details view of the file
Intent showDetailsIntent = null;
if (PreviewImageFragment.canBePreviewed(download.getFile())) {
showDetailsIntent = new Intent(this, PreviewImageActivity.class);
} else {
showDetailsIntent = new Intent(this, FileDisplayActivity.class);
}
showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile());
showDetailsIntent.putExtra(FileActivity.EXTRA_USER, download.getUser());
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
showDetailsIntent, PendingIntent.FLAG_IMMUTABLE));
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
if (mNotificationManager != null) {
mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
}
}
/**
* Callback method to update the progress bar in the status notification.
*/
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar,
long totalToTransfer, String filePath) {
int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
if (percent != mLastPercent) {
mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName);
mNotificationBuilder.setContentText(text);
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
if (mNotificationManager != null) {
mNotificationManager.notify(R.string.downloader_download_in_progress_ticker,
mNotificationBuilder.build());
}
}
mLastPercent = percent;
}
/**
* Updates the status notification with the result of a download operation.
*
* @param downloadResult Result of the download operation.
* @param download Finished download operation
*/
@SuppressFBWarnings("DMI")
private void notifyDownloadResult(DownloadFileOperation download,
RemoteOperationResult downloadResult) {
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
if (!downloadResult.isCancelled()) {
if (downloadResult.isSuccess()) {
if (conflictUploadId > 0) {
uploadsStorageManager.removeUpload(conflictUploadId);
}
// Dont show notification except an error has occured.
return;
}
int tickerId = downloadResult.isSuccess() ?
R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker;
boolean needsToUpdateCredentials = ResultCode.UNAUTHORIZED == downloadResult.getCode();
tickerId = needsToUpdateCredentials ?
R.string.downloader_download_failed_credentials_error : tickerId;
mNotificationBuilder
.setTicker(getString(tickerId))
.setContentTitle(getString(tickerId))
.setAutoCancel(true)
.setOngoing(false)
.setProgress(0, 0, false);
if (needsToUpdateCredentials) {
configureUpdateCredentialsNotification(download.getUser());
} else {
// TODO put something smart in showDetailsIntent
Intent showDetailsIntent = new Intent();
mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
showDetailsIntent, PendingIntent.FLAG_IMMUTABLE));
}
mNotificationBuilder.setContentText(ErrorMessageAdapter.getErrorCauseMessage(downloadResult,
download, getResources()));
if (mNotificationManager != null) {
mNotificationManager.notify((new SecureRandom()).nextInt(), mNotificationBuilder.build());
// Remove success notification
if (downloadResult.isSuccess()) {
// Sleep 2 seconds, so show the notification before remove it
NotificationUtils.cancelWithDelay(mNotificationManager,
R.string.downloader_download_succeeded_ticker, 2000);
}
}
}
}
private void configureUpdateCredentialsNotification(User user) {
// let the user update credentials with one click
Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount());
updateAccountCredentials.putExtra(
AuthenticatorActivity.EXTRA_ACTION,
AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
);
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
mNotificationBuilder.setContentIntent(
PendingIntent.getActivity(this,
(int) System.currentTimeMillis(),
updateAccountCredentials,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)
);
}
/**
* Sends a broadcast when a download finishes in order to the interested activities can
* update their view
*
* @param download Finished download operation
* @param downloadResult Result of the download operation
* @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
*/
private void sendBroadcastDownloadFinished(
DownloadFileOperation download,
RemoteOperationResult downloadResult,
String unlinkedFromRemotePath) {
Intent end = new Intent(getDownloadFinishMessage());
end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
end.putExtra(ACCOUNT_NAME, download.getUser().getAccountName());
end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
end.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.getBehaviour());
end.putExtra(SendShareDialog.ACTIVITY_NAME, download.getActivityName());
end.putExtra(SendShareDialog.PACKAGE_NAME, download.getPackageName());
if (unlinkedFromRemotePath != null) {
end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
}
end.setPackage(getPackageName());
localBroadcastManager.sendBroadcast(end);
}
/**
* Sends a broadcast when a new download is added to the queue.
*
* @param download Added download operation
* @param linkedToRemotePath Path in the downloads tree where the download was linked to
*/
private void sendBroadcastNewDownload(DownloadFileOperation download,
String linkedToRemotePath) {
Intent added = new Intent(getDownloadAddedMessage());
added.putExtra(ACCOUNT_NAME, download.getUser().getAccountName());
added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
added.setPackage(getPackageName());
localBroadcastManager.sendBroadcast(added);
}
private void cancelPendingDownloads(String accountName) {
mPendingDownloads.remove(accountName);
}
}

View file

@ -43,8 +43,12 @@ public class IndexedForest<V> {
private ConcurrentMap<String, Node<V>> mMap = new ConcurrentHashMap<>();
public ConcurrentMap<String, Node<V>> getAll() {
return mMap;
}
@SuppressWarnings("PMD.ShortClassName")
private class Node<V> {
public class Node<V> {
private String mKey;
private Node<V> mParent;
private Set<Node<V>> mChildren = new HashSet<>(); // TODO be careful with hash()

View file

@ -23,6 +23,7 @@ package com.owncloud.android.operations;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.nextcloud.client.account.User;
@ -43,6 +44,7 @@ import com.owncloud.android.utils.FileStorageUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@ -62,7 +64,7 @@ public class DownloadFileOperation extends RemoteOperation {
private String packageName;
private DownloadType downloadType;
private Context context;
private final WeakReference<Context> context;
private Set<OnDatatransferProgressListener> dataTransferListeners = new HashSet<>();
private long modificationTimestamp;
private DownloadFileRemoteOperation downloadOperation;
@ -90,7 +92,7 @@ public class DownloadFileOperation extends RemoteOperation {
this.behaviour = behaviour;
this.activityName = activityName;
this.packageName = packageName;
this.context = context;
this.context = new WeakReference<>(context);
this.downloadType = downloadType;
}
@ -98,6 +100,16 @@ public class DownloadFileOperation extends RemoteOperation {
this(user, file, null, null, null, context, DownloadType.DOWNLOAD);
}
public boolean isMatching(String accountName, long fileId) {
return getFile().getFileId() == fileId && getUser().getAccountName().equals(accountName);
}
public void cancelMatchingOperation(String accountName, long fileId) {
if (isMatching(accountName, fileId)) {
cancel();
}
}
public String getSavePath() {
if (file.getStoragePath() != null) {
File parentFile = new File(file.getStoragePath()).getParentFile();
@ -160,6 +172,11 @@ public class DownloadFileOperation extends RemoteOperation {
}
}
Context operationContext = context.get();
if (operationContext == null) {
return new RemoteOperationResult(RemoteOperationResult.ResultCode.UNKNOWN_ERROR);
}
RemoteOperationResult result;
File newFile = null;
boolean moved;
@ -180,6 +197,8 @@ public class DownloadFileOperation extends RemoteOperation {
result = downloadOperation.execute(client);
if (result.isSuccess()) {
modificationTimestamp = downloadOperation.getModificationTimestamp();
etag = downloadOperation.getEtag();
@ -194,13 +213,13 @@ public class DownloadFileOperation extends RemoteOperation {
// decrypt file
if (file.isEncrypted()) {
FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(user, context.getContentResolver());
FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(user, operationContext.getContentResolver());
OCFile parent = fileDataStorageManager.getFileByPath(file.getParentRemotePath());
OCFile parent = fileDataStorageManager.getFileByEncryptedRemotePath(file.getParentRemotePath());
DecryptedFolderMetadata metadata = EncryptionUtils.downloadFolderMetadata(parent,
client,
context,
operationContext,
user);
if (metadata == null) {
@ -218,7 +237,7 @@ public class DownloadFileOperation extends RemoteOperation {
key,
iv,
authenticationTag,
new ArbitraryDataProviderImpl(context),
new ArbitraryDataProviderImpl(operationContext),
user);
try (FileOutputStream fileOutputStream = new FileOutputStream(tmpFile)) {
@ -238,7 +257,7 @@ public class DownloadFileOperation extends RemoteOperation {
} else if (downloadType == DownloadType.EXPORT) {
new FileExportUtils().exportFile(file.getFileName(),
file.getMimeType(),
context.getContentResolver(),
operationContext.getContentResolver(),
null,
tmpFile);
if (!tmpFile.delete()) {
@ -246,6 +265,7 @@ public class DownloadFileOperation extends RemoteOperation {
}
}
}
Log_OC.i(TAG, "Download of " + file.getRemotePath() + " to " + getSavePath() + ": " +
result.getLogMessage());
@ -260,7 +280,7 @@ public class DownloadFileOperation extends RemoteOperation {
}
public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
public void addDownloadDataTransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (dataTransferListeners) {
dataTransferListeners.add(listener);
}

View file

@ -22,7 +22,11 @@
package com.owncloud.android.operations
enum class DownloadType {
DOWNLOAD,
EXPORT
enum class DownloadType(var type: String) {
DOWNLOAD("DOWNLOAD"),
EXPORT("EXPORT");
override fun toString(): String {
return type
}
}

View file

@ -22,13 +22,12 @@
package com.owncloud.android.operations;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.OwnCloudClient;
@ -310,33 +309,19 @@ public class SynchronizeFileOperation extends SyncOperation {
mTransferWasRequested = true;
}
/**
* Requests for a download to the FileDownloader service
*
* @param file OCFile object representing the file to download
*/
private void requestForDownload(OCFile file) {
Intent i = new Intent(mContext, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, mUser);
i.putExtra(FileDownloader.EXTRA_FILE, file);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
mContext.startForegroundService(i);
} else {
mContext.startService(i);
}
FileDownloadHelper.Companion.instance().downloadFile(
mUser,
file);
mTransferWasRequested = true;
}
public boolean transferWasRequested() {
return mTransferWasRequested;
}
public OCFile getLocalFile() {
return mLocalFile;
}
}

View file

@ -25,10 +25,10 @@ import android.content.Intent;
import android.text.TextUtils;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@ -376,19 +376,8 @@ public class SynchronizeFolderOperation extends SyncOperation {
}
}
private void classifyFileForLaterSyncOrDownload(OCFile remoteFile, OCFile localFile)
throws OperationCancelledException {
if (remoteFile.isFolder()) {
/// to download children files recursively
synchronized (mCancellationRequested) {
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
}
startSyncFolderOperation(remoteFile.getRemotePath());
}
} else {
/// prepare content synchronization for files (any file, not just favorites)
private void classifyFileForLaterSyncOrDownload(OCFile remoteFile, OCFile localFile) {
if (!remoteFile.isFolder()) {
SynchronizeFileOperation operation = new SynchronizeFileOperation(
localFile,
remoteFile,
@ -405,18 +394,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder, false);
for (OCFile child : children) {
/// classify file to sync/download contents later
if (child.isFolder()) {
/// to download children files recursively
synchronized(mCancellationRequested) {
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
}
startSyncFolderOperation(child.getRemotePath());
}
} else {
/// synchronization for regular files
if (!child.isFolder()) {
if (!child.isDown()) {
mFilesForDirectDownload.add(child);
@ -433,34 +411,18 @@ public class SynchronizeFolderOperation extends SyncOperation {
mFilesToSyncContents.add(operation);
}
}
}
}
private void syncContents() throws OperationCancelledException {
startDirectDownloads();
startContentSynchronizations(mFilesToSyncContents);
}
private void startDirectDownloads() throws OperationCancelledException {
for (OCFile file : mFilesForDirectDownload) {
synchronized(mCancellationRequested) {
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
}
Intent i = new Intent(mContext, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, user);
i.putExtra(FileDownloader.EXTRA_FILE, file);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
mContext.startForegroundService(i);
} else {
mContext.startService(i);
}
}
}
private void startDirectDownloads() {
FileDownloadHelper.Companion.instance().downloadFile(user, mLocalFolder);
}
/**

View file

@ -52,7 +52,6 @@ import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.OwnCloudAccount;
@ -308,7 +307,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
/**
* Updates the OC File after a successful download.
*
* TODO unify with code from {@link FileDownloader} and {@link DownloadTask}.
* TODO unify with code from {@link com.nextcloud.client.files.downloader.FileDownloadWorker} and {@link DownloadTask}.
*/
private void saveDownloadedFile(FileDataStorageManager storageManager, DownloadFileOperation dfo, OCFile file) {
long syncDate = System.currentTimeMillis();

View file

@ -28,8 +28,8 @@ import android.os.Message;
import android.util.Pair;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.IndexedForest;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
@ -169,9 +169,9 @@ class SyncFolderHandler extends Handler {
* this is a fast and ugly patch.
*/
private void sendBroadcastNewSyncFolder(Account account, String remotePath) {
Intent added = new Intent(FileDownloader.getDownloadAddedMessage());
added.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
added.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
Intent added = new Intent(FileDownloadWorker.Companion.getDownloadAddedMessage());
added.putExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME, account.name);
added.putExtra(FileDownloadWorker.EXTRA_REMOTE_PATH, remotePath);
added.setPackage(mService.getPackageName());
LocalBroadcastManager.getInstance(mService.getApplicationContext()).sendBroadcast(added);
}
@ -182,10 +182,10 @@ class SyncFolderHandler extends Handler {
*/
private void sendBroadcastFinishedSyncFolder(Account account, String remotePath,
boolean success) {
Intent finished = new Intent(FileDownloader.getDownloadFinishMessage());
finished.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
finished.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
finished.putExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, success);
Intent finished = new Intent(FileDownloadWorker.Companion.getDownloadFinishMessage());
finished.putExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME, account.name);
finished.putExtra(FileDownloadWorker.EXTRA_REMOTE_PATH, remotePath);
finished.putExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, success);
finished.setPackage(mService.getPackageName());
LocalBroadcastManager.getInstance(mService.getApplicationContext()).sendBroadcast(finished);
}

View file

@ -20,8 +20,8 @@
package com.owncloud.android.ui.activity;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
@ -30,11 +30,11 @@ public interface ComponentsGetter {
/**
* To be invoked when the parent activity is fully created to get a reference
* to the FileDownloader service API.
* to the FileDownloadWorker.
*/
public FileDownloaderBinder getFileDownloaderBinder();
public FileDownloadWorker.FileDownloadProgressListener getFileDownloadProgressListener();
/**
* To be invoked when the parent activity is fully created to get a reference
* to the FileUploader service API.

View file

@ -21,13 +21,13 @@ import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FileDownloadHelper
import com.nextcloud.model.HTTPStatusCodes
import com.nextcloud.utils.extensions.getParcelableArgument
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.db.OCUpload
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.files.services.FileUploader
import com.owncloud.android.files.services.NameCollisionPolicy
import com.owncloud.android.lib.common.utils.Log_OC
@ -114,11 +114,13 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
Decision.KEEP_SERVER -> if (!shouldDeleteLocal()) {
// Overwrite local file
val intent = Intent(baseContext, FileDownloader::class.java)
intent.putExtra(FileDownloader.EXTRA_USER, getUser().orElseThrow { RuntimeException() })
intent.putExtra(FileDownloader.EXTRA_FILE, file)
intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId)
startService(intent)
file?.let {
FileDownloadHelper.instance().downloadFile(
getUser().orElseThrow { RuntimeException() },
file,
conflictUploadId = conflictUploadId
)
}
} else {
uploadsStorageManager!!.removeUpload(upload)
}

View file

@ -43,6 +43,8 @@ import android.text.TextUtils;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.utils.EditorUtils;
@ -54,8 +56,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.common.OwnCloudAccount;
@ -166,9 +166,8 @@ public abstract class FileActivity extends DrawerActivity
private boolean mResumed;
protected FileDownloaderBinder mDownloaderBinder;
protected FileDownloadWorker.FileDownloadProgressListener fileDownloadProgressListener;
protected FileUploaderBinder mUploaderBinder;
private ServiceConnection mDownloadServiceConnection;
private ServiceConnection mUploadServiceConnection;
@Inject
@ -206,6 +205,7 @@ public abstract class FileActivity extends DrawerActivity
super.onCreate(savedInstanceState);
mHandler = new Handler();
mFileOperationsHelper = new FileOperationsHelper(this, getUserAccountManager(), connectivityService, editorUtils);
User user = null;
if (savedInstanceState != null) {
mFile = BundleExtensionsKt.getParcelableArgument(savedInstanceState, FileActivity.EXTRA_FILE, OCFile.class);
@ -218,25 +218,20 @@ public abstract class FileActivity extends DrawerActivity
viewThemeUtils.files.themeActionBar(this, actionBar, savedInstanceState.getString(KEY_ACTION_BAR_TITLE));
}
} else {
User user = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_USER, User.class);
user = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_USER, User.class);
mFile = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_FILE, OCFile.class);
mFromNotification = getIntent().getBooleanExtra(FileActivity.EXTRA_FROM_NOTIFICATION,
false);
if (user != null) {
setUser(user);
}
}
mOperationsServiceConnection = new OperationsServiceConnection();
bindService(new Intent(this, OperationsService.class), mOperationsServiceConnection,
Context.BIND_AUTO_CREATE);
mDownloadServiceConnection = newTransferenceServiceConnection();
if (mDownloadServiceConnection != null) {
bindService(new Intent(this, FileDownloader.class), mDownloadServiceConnection,
Context.BIND_AUTO_CREATE);
}
mUploadServiceConnection = newTransferenceServiceConnection();
if (mUploadServiceConnection != null) {
bindService(new Intent(this, FileUploader.class), mUploadServiceConnection,
@ -280,10 +275,6 @@ public abstract class FileActivity extends DrawerActivity
unbindService(mOperationsServiceConnection);
mOperationsServiceBinder = null;
}
if (mDownloadServiceConnection != null) {
unbindService(mDownloadServiceConnection);
mDownloadServiceConnection = null;
}
if (mUploadServiceConnection != null) {
unbindService(mUploadServiceConnection);
mUploadServiceConnection = null;
@ -616,8 +607,8 @@ public abstract class FileActivity extends DrawerActivity
}
@Override
public FileDownloaderBinder getFileDownloaderBinder() {
return mDownloaderBinder;
public FileDownloadWorker.FileDownloadProgressListener getFileDownloadProgressListener() {
return fileDownloadProgressListener;
}
@Override

View file

@ -65,12 +65,16 @@ import com.nextcloud.client.core.AsyncRunner;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.files.DeepLinkHandler;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.nextcloud.client.media.PlayerServiceConnection;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.utils.IntentUtil;
import com.nextcloud.java.util.Optional;
import com.nextcloud.model.WorkerState;
import com.nextcloud.model.WorkerStateLiveData;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
import com.nextcloud.utils.view.FastScrollUtils;
@ -80,8 +84,6 @@ import com.owncloud.android.databinding.FilesBinding;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.VirtualFolderType;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.files.services.NameCollisionPolicy;
@ -94,6 +96,7 @@ import com.owncloud.android.lib.resources.files.RestoreFileVersionRemoteOperatio
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
import com.owncloud.android.operations.CopyFileOperation;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.DownloadType;
import com.owncloud.android.operations.MoveFileOperation;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.RemoveFileOperation;
@ -284,6 +287,7 @@ public class FileDisplayActivity extends FileActivity
checkStoragePath();
initSyncBroadcastReceiver();
observeWorkerState();
}
@SuppressWarnings("unchecked")
@ -684,12 +688,12 @@ public class FileDisplayActivity extends FileActivity
// the user browsed to other file ; forget the automatic preview
mWaitingToPreview = null;
} else if (downloadEvent.equals(FileDownloader.getDownloadAddedMessage())) {
} else if (downloadEvent.equals(FileDownloadWorker.Companion.getDownloadAddedMessage())) {
// grant that the details fragment updates the progress bar
detailsFragment.listenForTransferProgress();
detailsFragment.updateFileDetails(true, false);
} else if (downloadEvent.equals(FileDownloader.getDownloadFinishMessage())) {
} else if (downloadEvent.equals(FileDownloadWorker.Companion.getDownloadFinishMessage())) {
// update the details panel
boolean detailsFragmentChanged = false;
if (waitedPreview) {
@ -1115,8 +1119,8 @@ public class FileDisplayActivity extends FileActivity
localBroadcastManager.registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
// Listen for download messages
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadAddedMessage());
downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage());
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloadWorker.Companion.getDownloadAddedMessage());
downloadIntentFilter.addAction(FileDownloadWorker.Companion.getDownloadFinishMessage());
mDownloadFinishReceiver = new DownloadFinishReceiver();
localBroadcastManager.registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
@ -1417,7 +1421,7 @@ public class FileDisplayActivity extends FileActivity
/**
* Class waiting for broadcast events from the {@link FileDownloader} service.
* Class waiting for broadcast events from the {@link FileDownloadWorker} service.
* <p>
* Updates the UI when a download is started or finished, provided that it is relevant for the current folder.
*/
@ -1426,16 +1430,16 @@ public class FileDisplayActivity extends FileActivity
@Override
public void onReceive(Context context, Intent intent) {
boolean sameAccount = isSameAccount(intent);
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
String downloadedRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_REMOTE_PATH);
String downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
boolean isDescendant = isDescendant(downloadedRemotePath);
if (sameAccount && isDescendant) {
String linkedToRemotePath = intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH);
String linkedToRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_LINKED_TO_PATH);
if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
updateListOfFilesFragment(false);
}
refreshDetailsFragmentIfVisible(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));
refreshDetailsFragmentIfVisible(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, false));
}
if (mWaitingToSend != null) {
@ -1468,7 +1472,7 @@ public class FileDisplayActivity extends FileActivity
}
private boolean isSameAccount(Intent intent) {
String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
String accountName = intent.getStringExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME);
return accountName != null && getAccount() != null && accountName.equals(getAccount().name);
}
}
@ -1558,6 +1562,24 @@ public class FileDisplayActivity extends FileActivity
return isRoot(getCurrentDir());
}
private void observeWorkerState() {
WorkerStateLiveData.Companion.instance().observe(this, state -> {
if (state instanceof WorkerState.Download) {
Log_OC.d(TAG, "Download worker started");
handleDownloadWorkerState();
}
});
}
private void handleDownloadWorkerState() {
if (mWaitingToPreview != null && getStorageManager() != null) {
mWaitingToPreview = getStorageManager().getFileById(mWaitingToPreview.getFileId());
if (mWaitingToPreview != null && !mWaitingToPreview.isDown()) {
requestForDownload();
}
}
}
@Override
protected ServiceConnection newTransferenceServiceConnection() {
return new ListServiceConnection();
@ -1570,22 +1592,13 @@ public class FileDisplayActivity extends FileActivity
@Override
public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {
Log_OC.d(TAG, "Download service connected");
mDownloaderBinder = (FileDownloaderBinder) service;
if (mWaitingToPreview != null && getStorageManager() != null) {
// update the file
mWaitingToPreview = getStorageManager().getFileById(mWaitingToPreview.getFileId());
if (mWaitingToPreview != null && !mWaitingToPreview.isDown()) {
requestForDownload();
}
}
} else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploaderBinder) service;
} else {
return;
}
// a new chance to get the mDownloadBinder through
// getFileDownloadBinder() - THIS IS A MESS
OCFileListFragment listOfFiles = getListOfFilesFragment();
@ -1593,9 +1606,9 @@ public class FileDisplayActivity extends FileActivity
IntentExtensionsKt.getParcelableArgument(getIntent(), EXTRA_FILE, OCFile.class) == null))) {
listOfFiles.listDirectory(MainApp.isOnlyOnDevice(), false);
}
Fragment leftFragment = getLeftFragment();
if (leftFragment instanceof FileDetailFragment) {
FileDetailFragment detailFragment = (FileDetailFragment) leftFragment;
if (leftFragment instanceof FileDetailFragment detailFragment) {
detailFragment.listenForTransferProgress();
detailFragment.updateFileDetails(false, false);
}
@ -1603,9 +1616,9 @@ public class FileDisplayActivity extends FileActivity
@Override
public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloadWorker.class))) {
Log_OC.d(TAG, "Download service disconnected");
mDownloaderBinder = null;
fileDownloadProgressListener = null;
} else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service disconnected");
mUploaderBinder = null;
@ -1883,13 +1896,7 @@ public class FileDisplayActivity extends FileActivity
private void requestForDownload() {
User user = getUser().orElseThrow(RuntimeException::new);
//if (!mWaitingToPreview.isDownloading()) {
if (!mDownloaderBinder.isDownloading(user, mWaitingToPreview)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, user);
i.putExtra(FileDownloader.EXTRA_FILE, mWaitingToPreview);
startService(i);
}
FileDownloadHelper.Companion.instance().downloadFileIfNotStartedBefore(user, mWaitingToPreview);
}
@Override
@ -1959,14 +1966,8 @@ public class FileDisplayActivity extends FileActivity
private void requestForDownload(OCFile file, String downloadBehaviour, String packageName, String activityName) {
final User currentUser = getUser().orElseThrow(RuntimeException::new);
if (!mDownloaderBinder.isDownloading(currentUser, mWaitingToPreview)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, currentUser);
i.putExtra(FileDownloader.EXTRA_FILE, file);
i.putExtra(SendShareDialog.PACKAGE_NAME, packageName);
i.putExtra(SendShareDialog.ACTIVITY_NAME, activityName);
i.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, downloadBehaviour);
startService(i);
if (!FileDownloadHelper.Companion.instance().isDownloading(currentUser, file)) {
FileDownloadHelper.Companion.instance().downloadFile(currentUser, file, downloadBehaviour, DownloadType.DOWNLOAD, activityName, packageName, null);
}
}

View file

@ -41,9 +41,12 @@ import android.view.View;
import com.google.common.collect.Sets;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.nextcloud.java.util.Optional;
import com.nextcloud.model.WorkerState;
import com.nextcloud.model.WorkerStateLiveData;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
@ -51,11 +54,11 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.DownloadFileOperation;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.ui.adapter.UserListAdapter;
import com.owncloud.android.ui.adapter.UserListItem;
@ -105,7 +108,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
private final Handler handler = new Handler();
private String accountName;
private UserListAdapter userListAdapter;
private ServiceConnection downloadServiceConnection;
private ServiceConnection uploadServiceConnection;
private Set<String> originalUsers;
private String originalCurrentUser;
@ -113,6 +115,9 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
private ArbitraryDataProvider arbitraryDataProvider;
private boolean multipleAccountsSupported;
private String workerAccountName;
private DownloadFileOperation workerCurrentDownload;
@Inject BackgroundJobManager backgroundJobManager;
@Inject UserAccountManager accountManager;
@ -160,6 +165,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
recyclerView.setAdapter(userListAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
initializeComponentGetters();
observeWorkerState();
}
@ -241,11 +247,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
* Initialize ComponentsGetters.
*/
private void initializeComponentGetters() {
downloadServiceConnection = newTransferenceServiceConnection();
if (downloadServiceConnection != null) {
bindService(new Intent(this, FileDownloader.class), downloadServiceConnection,
Context.BIND_AUTO_CREATE);
}
uploadServiceConnection = newTransferenceServiceConnection();
if (uploadServiceConnection != null) {
bindService(new Intent(this, FileUploader.class), uploadServiceConnection,
@ -340,9 +341,8 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
if (mUploaderBinder != null) {
mUploaderBinder.cancel(accountName);
}
if (mDownloaderBinder != null) {
mDownloaderBinder.cancel(accountName);
}
FileDownloadHelper.Companion.instance().cancelAllDownloadsForAccount(workerAccountName, workerCurrentDownload);
}
User currentUser = getUserAccountManager().getUser();
@ -374,10 +374,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
@Override
protected void onDestroy() {
if (downloadServiceConnection != null) {
unbindService(downloadServiceConnection);
downloadServiceConnection = null;
}
if (uploadServiceConnection != null) {
unbindService(uploadServiceConnection);
uploadServiceConnection = null;
@ -435,9 +431,8 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
if (mUploaderBinder != null) {
mUploaderBinder.cancel(user);
}
if (mDownloaderBinder != null) {
mDownloaderBinder.cancel(user.getAccountName());
}
FileDownloadHelper.Companion.instance().cancelAllDownloadsForAccount(workerAccountName, workerCurrentDownload);
backgroundJobManager.startAccountRemovalJob(user.getAccountName(), false);
@ -522,6 +517,16 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
}
}
private void observeWorkerState() {
WorkerStateLiveData.Companion.instance().observe(this, state -> {
if (state instanceof WorkerState.Download) {
Log_OC.d(TAG, "Download worker started");
workerAccountName = ((WorkerState.Download) state).getUser().getAccountName();
workerCurrentDownload = ((WorkerState.Download) state).getCurrentDownload();
}
});
}
@Override
public void onAccountClicked(User user) {
openAccount(user);
@ -534,11 +539,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
@Override
public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FileDownloader.class))) {
mDownloaderBinder = (FileDownloader.FileDownloaderBinder) service;
} else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploader.FileUploaderBinder) service;
}
@ -546,10 +547,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
@Override
public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FileDownloader.class))) {
Log_OC.d(TAG, "Download service suddenly disconnected");
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service suddenly disconnected");
mUploaderBinder = null;
}

View file

@ -32,6 +32,7 @@ import androidx.core.content.res.ResourcesCompat
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FileDownloadHelper
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.extensions.createRoundedOutline
import com.owncloud.android.R
@ -340,25 +341,33 @@ class OCFileListDelegate(
private fun showLocalFileIndicator(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
val operationsServiceBinder = transferServiceGetter.operationsServiceBinder
val fileDownloaderBinder = transferServiceGetter.fileDownloaderBinder
val fileUploaderBinder = transferServiceGetter.fileUploaderBinder
when {
val icon: Int? = when {
operationsServiceBinder?.isSynchronizing(user, file) == true ||
fileDownloaderBinder?.isDownloading(user, file) == true ||
FileDownloadHelper.instance().isDownloading(user, file) ||
fileUploaderBinder?.isUploading(user, file) == true -> {
// synchronizing, downloading or uploading
gridViewHolder.localFileIndicator.setImageResource(R.drawable.ic_synchronizing)
gridViewHolder.localFileIndicator.visibility = View.VISIBLE
R.drawable.ic_synchronizing
}
file.etagInConflict != null -> {
// conflict
gridViewHolder.localFileIndicator.setImageResource(R.drawable.ic_synchronizing_error)
gridViewHolder.localFileIndicator.visibility = View.VISIBLE
R.drawable.ic_synchronizing_error
}
file.isDown -> {
// downloaded
gridViewHolder.localFileIndicator.setImageResource(R.drawable.ic_synced)
gridViewHolder.localFileIndicator.visibility = View.VISIBLE
R.drawable.ic_synced
}
else -> {
null
}
}
gridViewHolder.localFileIndicator.run {
icon?.let {
setImageResource(icon)
visibility = View.VISIBLE
}
}
}

View file

@ -44,6 +44,7 @@ import com.google.android.material.tabs.TabLayout;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.network.ConnectivityService;
@ -57,7 +58,6 @@ import com.owncloud.android.databinding.FileDetailsFragmentBinding;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
@ -502,7 +502,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
* TODO Remove parameter when the transferring state of files is kept in database.
*
* @param transferring Flag signaling if the file should be considered as downloading or uploading, although
* {@link FileDownloaderBinder#isDownloading(User, OCFile)} and
* {@link FileDownloadHelper#isDownloading(User, OCFile)} and
* {@link FileUploaderBinder#isUploading(User, OCFile)} return false.
* @param refresh If 'true', try to refresh the whole file from the database
*/
@ -534,10 +534,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
setFavoriteIconStatus(file.isFavorite());
// configure UI for depending upon local state of the file
FileDownloaderBinder downloaderBinder = containerActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
if (transferring
|| (downloaderBinder != null && downloaderBinder.isDownloading(user, file))
|| (FileDownloadHelper.Companion.instance().isDownloading(user, file))
|| (uploaderBinder != null && uploaderBinder.isUploading(user, file))) {
setButtonsForTransferring();
@ -659,10 +658,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
// show the progress bar for the transfer
binding.progressBlock.setVisibility(View.VISIBLE);
binding.progressText.setVisibility(View.VISIBLE);
FileDownloaderBinder downloaderBinder = containerActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
//if (getFile().isDownloading()) {
if (downloaderBinder != null && downloaderBinder.isDownloading(user, getFile())) {
if (FileDownloadHelper.Companion.instance().isDownloading(user, getFile())) {
binding.progressText.setText(R.string.downloader_download_in_progress_ticker);
} else {
if (uploaderBinder != null && uploaderBinder.isUploading(user, getFile())) {
@ -694,9 +691,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
public void listenForTransferProgress() {
if (progressListener != null) {
if (containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().
addDatatransferProgressListener(progressListener, getFile());
if (containerActivity.getFileDownloadProgressListener() != null) {
containerActivity.getFileDownloadProgressListener().
addDataTransferProgressListener(progressListener, getFile());
}
if (containerActivity.getFileUploaderBinder() != null) {
containerActivity.getFileUploaderBinder().
@ -709,9 +706,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
private void leaveTransferProgress() {
if (progressListener != null) {
if (containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().
removeDatatransferProgressListener(progressListener, getFile());
if (containerActivity.getFileDownloadProgressListener() != null) {
containerActivity.getFileDownloadProgressListener().
removeDataTransferProgressListener(progressListener, getFile());
}
if (containerActivity.getFileUploaderBinder() != null) {
containerActivity.getFileUploaderBinder().

View file

@ -21,13 +21,11 @@
package com.owncloud.android.ui.fragment;
import android.accounts.Account;
import android.app.Activity;
import android.os.Bundle;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.ui.activity.ComponentsGetter;
@ -161,8 +159,8 @@ public class FileFragment extends Fragment {
* This happens when a download or upload is started or ended for a file.
*
* This method is necessary by now to update the user interface of the double-pane layout
* in tablets because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)}
* and {@link FileUploaderBinder#isUploading(Account, OCFile)}
* in tablets because methods {@link //FileDownloaderBinder # isDownloading(Account, OCFile)}
* and {@link FileUploaderBinder# isUploading(Account, OCFile)}
* won't provide the needed response before the method where this is called finishes.
*
* TODO Remove this when the transfer state of a file is kept in the database

View file

@ -38,11 +38,11 @@ import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.files.downloader.DownloadRequest;
import com.nextcloud.client.files.downloader.Request;
import com.nextcloud.client.files.downloader.Transfer;
import com.nextcloud.client.files.downloader.TransferManagerConnection;
import com.nextcloud.client.files.downloader.TransferState;
import com.nextcloud.client.files.DownloadRequest;
import com.nextcloud.client.files.Request;
import com.nextcloud.client.files.transfer.Transfer;
import com.nextcloud.client.files.transfer.TransferManagerConnection;
import com.nextcloud.client.files.transfer.TransferState;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.utils.extensions.BundleExtensionsKt;

View file

@ -47,6 +47,7 @@ import android.webkit.MimeTypeMap;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.java.util.Optional;
@ -58,7 +59,6 @@ import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.StreamMediaFileOperation;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@ -996,11 +996,11 @@ public class FileOperationsHelper {
}
}
// for both files and folders
FileDownloaderBinder downloaderBinder = fileActivity.getFileDownloaderBinder();
if (downloaderBinder != null && downloaderBinder.isDownloading(currentUser, file)) {
downloaderBinder.cancel(currentUser.toPlatformAccount(), file);
if (FileDownloadHelper.Companion.instance().isDownloading(currentUser, file)) {
List<OCFile> files = fileActivity.getStorageManager().getAllFilesRecursivelyInsideFolder(file);
FileDownloadHelper.Companion.instance().cancelPendingOrCurrentDownloads(currentUser, files);
}
FileUploaderBinder uploaderBinder = fileActivity.getFileUploaderBinder();
if (uploaderBinder != null && uploaderBinder.isUploading(currentUser, file)) {
uploaderBinder.cancel(currentUser.toPlatformAccount(), file);

View file

@ -261,8 +261,8 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
public void listenForTransferProgress() {
if (mProgressListener != null && !mListening && containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, getFile());
if (mProgressListener != null && !mListening && containerActivity.getFileDownloadProgressListener() != null) {
containerActivity.getFileDownloadProgressListener().addDataTransferProgressListener(mProgressListener, getFile());
mListening = true;
setButtonsForTransferring();
}
@ -270,9 +270,9 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
public void leaveTransferProgress() {
if (mProgressListener != null && containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder()
.removeDatatransferProgressListener(mProgressListener, getFile());
if (mProgressListener != null && containerActivity.getFileDownloadProgressListener() != null) {
containerActivity.getFileDownloadProgressListener()
.removeDataTransferProgressListener(mProgressListener, getFile());
mListening = false;
}
}

View file

@ -37,22 +37,25 @@ import android.view.View;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.java.util.Optional;
import com.nextcloud.model.WorkerState;
import com.nextcloud.model.WorkerStateLiveData;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.VirtualFolderType;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.DownloadType;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
import com.owncloud.android.ui.activity.FileActivity;
@ -99,6 +102,8 @@ public class PreviewImageActivity extends FileActivity implements
private DownloadFinishReceiver mDownloadFinishReceiver;
private UploadFinishReceiver mUploadFinishReceiver;
private View mFullScreenAnchorView;
private boolean isDownloadWorkStarted = false;
@Inject AppPreferences preferences;
@Inject LocalBroadcastManager localBroadcastManager;
@ -146,6 +151,8 @@ public class PreviewImageActivity extends FileActivity implements
} else {
mRequestWaitingForBinder = false;
}
observeWorkerState();
}
public void toggleActionBarVisibility(boolean hide) {
@ -299,6 +306,25 @@ public class PreviewImageActivity extends FileActivity implements
}
}
private void observeWorkerState() {
WorkerStateLiveData.Companion.instance().observe(this, state -> {
if (state instanceof WorkerState.Download) {
Log_OC.d(TAG, "Download worker started");
isDownloadWorkStarted = true;
if (mRequestWaitingForBinder) {
mRequestWaitingForBinder = false;
Log_OC.d(TAG, "Simulating reselection of current page after connection " +
"of download binder");
onPageSelected(mViewPager.getCurrentItem());
}
} else {
Log_OC.d(TAG, "Download worker stopped");
isDownloadWorkStarted = false;
}
});
}
@Override
protected ServiceConnection newTransferenceServiceConnection() {
return new PreviewImageServiceConnection();
@ -309,18 +335,7 @@ public class PreviewImageActivity extends FileActivity implements
@Override
public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(PreviewImageActivity.this,
FileDownloader.class))) {
mDownloaderBinder = (FileDownloaderBinder) service;
if (mRequestWaitingForBinder) {
mRequestWaitingForBinder = false;
Log_OC.d(TAG, "Simulating reselection of current page after connection " +
"of download binder");
onPageSelected(mViewPager.getCurrentItem());
}
} else if (component.equals(new ComponentName(PreviewImageActivity.this,
FileUploader.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploaderBinder) service;
@ -331,10 +346,6 @@ public class PreviewImageActivity extends FileActivity implements
@Override
public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(PreviewImageActivity.this,
FileDownloader.class))) {
Log_OC.d(TAG, "Download service suddenly disconnected");
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(PreviewImageActivity.this,
FileUploader.class))) {
Log_OC.d(TAG, "Upload service suddenly disconnected");
mUploaderBinder = null;
@ -359,7 +370,7 @@ public class PreviewImageActivity extends FileActivity implements
super.onResume();
mDownloadFinishReceiver = new DownloadFinishReceiver();
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadFinishMessage());
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloadWorker.Companion.getDownloadFinishMessage());
localBroadcastManager.registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
mUploadFinishReceiver = new UploadFinishReceiver();
@ -408,19 +419,8 @@ public class PreviewImageActivity extends FileActivity implements
}
public void requestForDownload(OCFile file, String downloadBehaviour) {
if (mDownloaderBinder == null) {
Log_OC.d(TAG, "requestForDownload called without binder to download service");
} else if (!mDownloaderBinder.isDownloading(getUserAccountManager().getUser(), file)) {
final User user = getUser().orElseThrow(RuntimeException::new);
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, user);
i.putExtra(FileDownloader.EXTRA_FILE, file);
if (downloadBehaviour != null) {
i.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, downloadBehaviour);
}
startService(i);
}
final User user = getUser().orElseThrow(RuntimeException::new);
FileDownloadHelper.Companion.instance().downloadFileIfNotStartedBefore(user, file);
}
/**
@ -433,7 +433,7 @@ public class PreviewImageActivity extends FileActivity implements
public void onPageSelected(int position) {
mSavedPosition = position;
mHasSavedPosition = true;
if (mDownloaderBinder == null) {
if (!isDownloadWorkStarted) {
mRequestWaitingForBinder = true;
} else {
OCFile currentFile = mPreviewImagePagerAdapter.getFileAt(position);
@ -484,7 +484,7 @@ public class PreviewImageActivity extends FileActivity implements
}
/**
* Class waiting for broadcast events from the {@link FileDownloader} service.
* Class waiting for broadcast events from the {@link FileDownloadWorker} service.
*
* Updates the UI when a download is started or finished, provided that it is relevant for the
* folder displayed in the gallery.
@ -504,12 +504,12 @@ public class PreviewImageActivity extends FileActivity implements
}
private void previewNewImage(Intent intent) {
String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
String accountName = intent.getStringExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME);
String downloadedRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_REMOTE_PATH);
String downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
if (getAccount().name.equals(accountName) && downloadedRemotePath != null) {
OCFile file = getStorageManager().getFileByPath(downloadedRemotePath);
boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false);
boolean downloadWasFine = intent.getBooleanExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, false);
if (EditImageActivity.OPEN_IMAGE_EDITOR.equals(downloadBehaviour)) {
startImageEditor(file);

View file

@ -19,7 +19,6 @@
*/
package com.owncloud.android.ui.preview;
import android.content.Intent;
import android.util.SparseArray;
import android.view.ViewGroup;

View file

@ -27,9 +27,7 @@
package com.owncloud.android.ui.preview
import android.app.Activity
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
@ -39,7 +37,6 @@ import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.view.Menu
import android.view.MenuItem
@ -65,6 +62,7 @@ import androidx.media3.ui.PlayerView
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.files.downloader.FileDownloadHelper
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.media.ExoplayerListener
import com.nextcloud.client.media.NextcloudExoPlayer.createNextcloudExoplayer
@ -80,13 +78,12 @@ import com.owncloud.android.databinding.ActivityPreviewMediaBinding
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.files.StreamMediaFileOperation
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.DownloadType
import com.owncloud.android.operations.RemoveFileOperation
import com.owncloud.android.operations.SynchronizeFileOperation
import com.owncloud.android.ui.activity.FileActivity
@ -563,33 +560,12 @@ class PreviewMediaActivity :
}
}
override fun newTransferenceServiceConnection(): ServiceConnection {
return PreviewMediaServiceConnection()
}
private fun onSynchronizeFileOperationFinish(result: RemoteOperationResult<*>?) {
result?.let {
invalidateOptionsMenu()
}
}
private inner class PreviewMediaServiceConnection : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName?, service: IBinder?) {
componentName?.let {
if (it == ComponentName(this@PreviewMediaActivity, FileDownloader::class.java)) {
mDownloaderBinder = service as FileDownloaderBinder
}
}
}
override fun onServiceDisconnected(componentName: ComponentName?) {
if (componentName == ComponentName(this@PreviewMediaActivity, FileDownloader::class.java)) {
Log_OC.d(PreviewImageActivity.TAG, "Download service suddenly disconnected")
mDownloaderBinder = null
}
}
}
override fun downloadFile(file: OCFile?, packageName: String?, activityName: String?) {
requestForDownload(file, OCFileListFragment.DOWNLOAD_SEND, packageName, activityName)
}
@ -600,21 +576,22 @@ class PreviewMediaActivity :
packageName: String? = null,
activityName: String? = null
) {
if (fileDownloaderBinder.isDownloading(user, file)) {
if (FileDownloadHelper.instance().isDownloading(user, file)) {
return
}
val intent = Intent(this, FileDownloader::class.java).apply {
putExtra(FileDownloader.EXTRA_USER, user)
putExtra(FileDownloader.EXTRA_FILE, file)
downloadBehavior?.let { behavior ->
putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, behavior)
user?.let { user ->
file?.let { file ->
FileDownloadHelper.instance().downloadFile(
user,
file,
downloadBehavior ?: "",
DownloadType.DOWNLOAD,
packageName ?: "",
activityName ?: ""
)
}
putExtra(SendShareDialog.PACKAGE_NAME, packageName)
putExtra(SendShareDialog.ACTIVITY_NAME, activityName)
}
startService(intent)
}
private fun seeDetails() {

View file

@ -53,6 +53,7 @@ import android.view.ViewGroup;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.media.ExoplayerListener;
import com.nextcloud.client.media.NextcloudExoPlayer;
@ -66,7 +67,6 @@ import com.owncloud.android.databinding.FragmentPreviewMediaBinding;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.files.StreamMediaFileOperation;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@ -478,12 +478,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
getView(),
backgroundJobManager);
} else if (itemId == R.id.action_download_file) {
if (!containerActivity.getFileDownloaderBinder().isDownloading(user, getFile())) {
Intent i = new Intent(requireActivity(), FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, user);
i.putExtra(FileDownloader.EXTRA_FILE, getFile());
requireActivity().startService(i);
}
FileDownloadHelper.Companion.instance().downloadFileIfNotStartedBefore(user, getFile());
}
}

View file

@ -42,11 +42,11 @@ import android.view.WindowManager;
import android.widget.EditText;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.PostUploadAction;
import com.nextcloud.client.files.downloader.Request;
import com.nextcloud.client.files.downloader.TransferManagerConnection;
import com.nextcloud.client.files.downloader.UploadRequest;
import com.nextcloud.client.files.downloader.UploadTrigger;
import com.nextcloud.client.files.Request;
import com.nextcloud.client.files.UploadRequest;
import com.nextcloud.client.files.transfer.TransferManagerConnection;
import com.nextcloud.client.files.upload.PostUploadAction;
import com.nextcloud.client.files.upload.UploadTrigger;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;

View file

@ -168,12 +168,16 @@
<string name="uploads_view_later_waiting_to_upload">Waiting to upload</string>
<string name="uploads_view_group_header" translatable="false">%1$s (%2$d)</string>
<string name="downloader_download_in_progress_ticker">Downloading…</string>
<string name="downloader_download_in_progress">%1$d%% %2$s</string>
<string name="downloader_download_in_progress_content">%1$d%% Downloading %2$s</string>
<string name="downloader_download_succeeded_ticker">Downloaded</string>
<string name="downloader_download_succeeded_content">%1$s downloaded</string>
<string name="downloader_download_failed_ticker">Download failed</string>
<string name="downloader_download_failed_content">Could not download %1$s</string>
<string name="downloader_not_downloaded_yet">Not downloaded yet</string>
<string name="downloader_file_download_cancelled">Certain files were canceled during the download by user</string>
<string name="downloader_file_download_failed">Error occurred while downloading files</string>
<string name="downloader_unexpected_error">Unexpected error occurred while downloading files</string>
<string name="downloader_download_failed_credentials_error">Download failed, log in again</string>
<string name="common_choose_account">Choose account</string>
<string name="common_switch_account">Switch account</string>
@ -636,7 +640,7 @@
<string name="resharing_is_not_allowed">Resharing is not allowed</string>
<string name="foreground_service_upload">Uploading files…</string>
<string name="foreground_service_download">Downloading files…</string>
<string name="worker_download">Downloading files…</string>
<string name="prefs_sourcecode">Get source code</string>
<string name="prefs_license">License</string>

View file

@ -11,7 +11,7 @@ buildscript {
androidLibraryVersion = "master-SNAPSHOT"
mockitoVersion = "4.11.0"
mockitoKotlinVersion = "4.1.0"
mockkVersion = "1.13.3"
mockkVersion = "1.13.8"
espressoVersion = "3.5.1"
workRuntime = "2.8.1"
fidoVersion = "4.1.0-patch2"