629 lines
24 KiB
Kotlin
629 lines
24 KiB
Kotlin
|
|
/*
|
||
|
|
* Nextcloud - Android Client
|
||
|
|
*
|
||
|
|
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
|
*/
|
||
|
|
package com.nextcloud.client.jobs
|
||
|
|
|
||
|
|
import android.provider.MediaStore
|
||
|
|
import androidx.lifecycle.LiveData
|
||
|
|
import androidx.lifecycle.map
|
||
|
|
import androidx.work.Constraints
|
||
|
|
import androidx.work.Data
|
||
|
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||
|
|
import androidx.work.ExistingWorkPolicy
|
||
|
|
import androidx.work.ListenableWorker
|
||
|
|
import androidx.work.NetworkType
|
||
|
|
import androidx.work.OneTimeWorkRequest
|
||
|
|
import androidx.work.Operation
|
||
|
|
import androidx.work.PeriodicWorkRequest
|
||
|
|
import androidx.work.WorkInfo
|
||
|
|
import androidx.work.WorkManager
|
||
|
|
import androidx.work.workDataOf
|
||
|
|
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.jobs.download.FileDownloadWorker
|
||
|
|
import com.nextcloud.client.jobs.upload.FileUploadWorker
|
||
|
|
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
|
||
|
|
import kotlin.reflect.KClass
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Note to maintainers
|
||
|
|
*
|
||
|
|
* Since [androidx.work.WorkManager] is missing API to easily attach worker metadata,
|
||
|
|
* we use tags API to attach our custom metadata.
|
||
|
|
*
|
||
|
|
* To create new job request, use [BackgroundJobManagerImpl.oneTimeRequestBuilder] and
|
||
|
|
* [BackgroundJobManagerImpl.periodicRequestBuilder] calls, instead of calling
|
||
|
|
* platform builders. Those methods will create builders pre-set with mandatory tags.
|
||
|
|
*
|
||
|
|
* Since Google is notoriously releasing new background job services, [androidx.work.WorkManager] API is
|
||
|
|
* considered private implementation detail and should not be leaked through the interface, to minimize
|
||
|
|
* potential migration cost in the future.
|
||
|
|
*/
|
||
|
|
@Suppress("TooManyFunctions") // we expect this implementation to have rich API
|
||
|
|
internal class BackgroundJobManagerImpl(
|
||
|
|
private val workManager: WorkManager,
|
||
|
|
private val clock: Clock,
|
||
|
|
private val preferences: AppPreferences
|
||
|
|
) : BackgroundJobManager, Injectable {
|
||
|
|
|
||
|
|
companion object {
|
||
|
|
|
||
|
|
const val TAG_ALL = "*" // This tag allows us to retrieve list of all jobs run by Nextcloud client
|
||
|
|
const val JOB_CONTENT_OBSERVER = "content_observer"
|
||
|
|
const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
|
||
|
|
const val JOB_IMMEDIATE_CONTACTS_BACKUP = "immediate_contacts_backup"
|
||
|
|
const val JOB_IMMEDIATE_CONTACTS_IMPORT = "immediate_contacts_import"
|
||
|
|
const val JOB_PERIODIC_CALENDAR_BACKUP = "periodic_calendar_backup"
|
||
|
|
const val JOB_IMMEDIATE_CALENDAR_IMPORT = "immediate_calendar_import"
|
||
|
|
const val JOB_PERIODIC_FILES_SYNC = "periodic_files_sync"
|
||
|
|
const val JOB_IMMEDIATE_FILES_SYNC = "immediate_files_sync"
|
||
|
|
const val JOB_PERIODIC_OFFLINE_SYNC = "periodic_offline_sync"
|
||
|
|
const val JOB_PERIODIC_MEDIA_FOLDER_DETECTION = "periodic_media_folder_detection"
|
||
|
|
const val JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION = "immediate_media_folder_detection"
|
||
|
|
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"
|
||
|
|
|
||
|
|
const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
|
||
|
|
const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
|
||
|
|
|
||
|
|
const val JOB_TEST = "test_job"
|
||
|
|
|
||
|
|
const val MAX_CONTENT_TRIGGER_DELAY_MS = 10000L
|
||
|
|
|
||
|
|
const val TAG_PREFIX_NAME = "name"
|
||
|
|
const val TAG_PREFIX_USER = "user"
|
||
|
|
const val TAG_PREFIX_CLASS = "class"
|
||
|
|
const val TAG_PREFIX_START_TIMESTAMP = "timestamp"
|
||
|
|
val PREFIXES = setOf(TAG_PREFIX_NAME, TAG_PREFIX_USER, TAG_PREFIX_START_TIMESTAMP, TAG_PREFIX_CLASS)
|
||
|
|
const val NOT_SET_VALUE = "not set"
|
||
|
|
const val PERIODIC_BACKUP_INTERVAL_MINUTES = 24 * 60L
|
||
|
|
const val DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES = 15L
|
||
|
|
const val DEFAULT_IMMEDIATE_JOB_DELAY_SEC = 3L
|
||
|
|
|
||
|
|
private const val KEEP_LOG_MILLIS = 1000 * 60 * 60 * 24 * 3L
|
||
|
|
|
||
|
|
fun formatNameTag(name: String, user: User? = null): String {
|
||
|
|
return if (user == null) {
|
||
|
|
"$TAG_PREFIX_NAME:$name"
|
||
|
|
} else {
|
||
|
|
"$TAG_PREFIX_NAME:$name ${user.accountName}"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fun formatUserTag(user: User): String = "$TAG_PREFIX_USER:${user.accountName}"
|
||
|
|
fun formatClassTag(jobClass: KClass<out ListenableWorker>): String = "$TAG_PREFIX_CLASS:${jobClass.simpleName}"
|
||
|
|
fun formatTimeTag(startTimestamp: Long): String = "$TAG_PREFIX_START_TIMESTAMP:$startTimestamp"
|
||
|
|
|
||
|
|
fun parseTag(tag: String): Pair<String, String>? {
|
||
|
|
val key = tag.substringBefore(":", "")
|
||
|
|
val value = tag.substringAfter(":", "")
|
||
|
|
return if (key in PREFIXES) {
|
||
|
|
key to value
|
||
|
|
} else {
|
||
|
|
null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fun parseTimestamp(timestamp: String): Date {
|
||
|
|
return try {
|
||
|
|
val ms = timestamp.toLong()
|
||
|
|
Date(ms)
|
||
|
|
} catch (ex: NumberFormatException) {
|
||
|
|
Date(0)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert platform [androidx.work.WorkInfo] object into application-specific [JobInfo] model.
|
||
|
|
* Conversion extracts work metadata from tags.
|
||
|
|
*/
|
||
|
|
fun fromWorkInfo(info: WorkInfo?): JobInfo? {
|
||
|
|
return if (info != null) {
|
||
|
|
val metadata = mutableMapOf<String, String>()
|
||
|
|
info.tags.forEach { parseTag(it)?.let { metadata[it.first] = it.second } }
|
||
|
|
val timestamp = parseTimestamp(metadata.get(TAG_PREFIX_START_TIMESTAMP) ?: "0")
|
||
|
|
JobInfo(
|
||
|
|
id = info.id,
|
||
|
|
state = info.state.toString(),
|
||
|
|
name = metadata.get(TAG_PREFIX_NAME) ?: NOT_SET_VALUE,
|
||
|
|
user = metadata.get(TAG_PREFIX_USER) ?: NOT_SET_VALUE,
|
||
|
|
started = timestamp,
|
||
|
|
progress = info.progress.getInt("progress", -1),
|
||
|
|
workerClass = metadata.get(TAG_PREFIX_CLASS) ?: NOT_SET_VALUE
|
||
|
|
)
|
||
|
|
} else {
|
||
|
|
null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fun deleteOldLogs(logEntries: MutableList<LogEntry>): MutableList<LogEntry> {
|
||
|
|
logEntries.removeIf {
|
||
|
|
return@removeIf (
|
||
|
|
it.started != null &&
|
||
|
|
Date(Date().time - KEEP_LOG_MILLIS).after(it.started)
|
||
|
|
) ||
|
||
|
|
(
|
||
|
|
it.finished != null &&
|
||
|
|
Date(Date().time - KEEP_LOG_MILLIS).after(it.finished)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return logEntries
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun logStartOfWorker(workerName: String?) {
|
||
|
|
val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
|
||
|
|
|
||
|
|
if (workerName == null) {
|
||
|
|
logs.add(LogEntry(Date(), null, null, NOT_SET_VALUE))
|
||
|
|
} else {
|
||
|
|
logs.add(LogEntry(Date(), null, null, workerName))
|
||
|
|
}
|
||
|
|
preferences.saveLogEntry(logs)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun logEndOfWorker(workerName: String?, result: ListenableWorker.Result) {
|
||
|
|
val logs = deleteOldLogs(preferences.readLogEntry().toMutableList())
|
||
|
|
if (workerName == null) {
|
||
|
|
logs.add(LogEntry(null, Date(), result.toString(), NOT_SET_VALUE))
|
||
|
|
} else {
|
||
|
|
logs.add(LogEntry(null, Date(), result.toString(), workerName))
|
||
|
|
}
|
||
|
|
preferences.saveLogEntry(logs)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create [OneTimeWorkRequest.Builder] pre-set with common attributes
|
||
|
|
*/
|
||
|
|
private fun oneTimeRequestBuilder(
|
||
|
|
jobClass: KClass<out ListenableWorker>,
|
||
|
|
jobName: String,
|
||
|
|
user: User? = null
|
||
|
|
): OneTimeWorkRequest.Builder {
|
||
|
|
val builder = OneTimeWorkRequest.Builder(jobClass.java)
|
||
|
|
.addTag(TAG_ALL)
|
||
|
|
.addTag(formatNameTag(jobName, user))
|
||
|
|
.addTag(formatTimeTag(clock.currentTime))
|
||
|
|
.addTag(formatClassTag(jobClass))
|
||
|
|
user?.let { builder.addTag(formatUserTag(it)) }
|
||
|
|
return builder
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create [PeriodicWorkRequest] pre-set with common attributes
|
||
|
|
*/
|
||
|
|
private fun periodicRequestBuilder(
|
||
|
|
jobClass: KClass<out ListenableWorker>,
|
||
|
|
jobName: String,
|
||
|
|
intervalMins: Long = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
|
||
|
|
flexIntervalMins: Long = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
|
||
|
|
user: User? = null
|
||
|
|
): PeriodicWorkRequest.Builder {
|
||
|
|
val builder = PeriodicWorkRequest.Builder(
|
||
|
|
jobClass.java,
|
||
|
|
intervalMins,
|
||
|
|
TimeUnit.MINUTES,
|
||
|
|
flexIntervalMins,
|
||
|
|
TimeUnit.MINUTES
|
||
|
|
)
|
||
|
|
.addTag(TAG_ALL)
|
||
|
|
.addTag(formatNameTag(jobName, user))
|
||
|
|
.addTag(formatTimeTag(clock.currentTime))
|
||
|
|
.addTag(formatClassTag(jobClass))
|
||
|
|
user?.let { builder.addTag(formatUserTag(it)) }
|
||
|
|
return builder
|
||
|
|
}
|
||
|
|
|
||
|
|
private fun WorkManager.getJobInfo(id: UUID): LiveData<JobInfo?> {
|
||
|
|
val workInfo = getWorkInfoByIdLiveData(id)
|
||
|
|
return workInfo.map { fromWorkInfo(it) }
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Cancel work using name tag with optional user scope.
|
||
|
|
* All work instances will be cancelled.
|
||
|
|
*/
|
||
|
|
private fun WorkManager.cancelJob(name: String, user: User? = null): Operation {
|
||
|
|
val tag = formatNameTag(name, user)
|
||
|
|
return cancelAllWorkByTag(tag)
|
||
|
|
}
|
||
|
|
|
||
|
|
override val jobs: LiveData<List<JobInfo>>
|
||
|
|
get() {
|
||
|
|
val workInfo = workManager.getWorkInfosByTagLiveData("*")
|
||
|
|
return workInfo.map { it -> it.map { fromWorkInfo(it) ?: JobInfo() }.sortedBy { it.started }.reversed() }
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun scheduleContentObserverJob() {
|
||
|
|
val constrains = Constraints.Builder()
|
||
|
|
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||
|
|
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||
|
|
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||
|
|
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
||
|
|
.setTriggerContentMaxDelay(MAX_CONTENT_TRIGGER_DELAY_MS, TimeUnit.MILLISECONDS)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER)
|
||
|
|
.setConstraints(constrains)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.REPLACE, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun schedulePeriodicContactsBackup(user: User) {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(ContactsBackupWork.KEY_ACCOUNT, user.accountName)
|
||
|
|
.putBoolean(ContactsBackupWork.KEY_FORCE, true)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val constraints = Constraints.Builder()
|
||
|
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = periodicRequestBuilder(
|
||
|
|
jobClass = ContactsBackupWork::class,
|
||
|
|
jobName = JOB_PERIODIC_CONTACTS_BACKUP,
|
||
|
|
intervalMins = PERIODIC_BACKUP_INTERVAL_MINUTES,
|
||
|
|
user = user
|
||
|
|
)
|
||
|
|
.setInputData(data)
|
||
|
|
.setConstraints(constraints)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_CONTACTS_BACKUP, ExistingPeriodicWorkPolicy.KEEP, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun cancelPeriodicContactsBackup(user: User) {
|
||
|
|
workManager.cancelJob(JOB_PERIODIC_CONTACTS_BACKUP, user)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateContactsImport(
|
||
|
|
contactsAccountName: String?,
|
||
|
|
contactsAccountType: String?,
|
||
|
|
vCardFilePath: String,
|
||
|
|
selectedContacts: IntArray
|
||
|
|
): LiveData<JobInfo?> {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(ContactsImportWork.ACCOUNT_NAME, contactsAccountName)
|
||
|
|
.putString(ContactsImportWork.ACCOUNT_TYPE, contactsAccountType)
|
||
|
|
.putString(ContactsImportWork.VCARD_FILE_PATH, vCardFilePath)
|
||
|
|
.putIntArray(ContactsImportWork.SELECTED_CONTACTS_INDICES, selectedContacts)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val constraints = Constraints.Builder()
|
||
|
|
.setRequiresCharging(false)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(ContactsImportWork::class, JOB_IMMEDIATE_CONTACTS_IMPORT)
|
||
|
|
.setInputData(data)
|
||
|
|
.setConstraints(constraints)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_IMMEDIATE_CONTACTS_IMPORT, ExistingWorkPolicy.KEEP, request)
|
||
|
|
|
||
|
|
return workManager.getJobInfo(request.id)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateCalendarImport(calendarPaths: Map<String, Int>): LiveData<JobInfo?> {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putAll(calendarPaths)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val constraints = Constraints.Builder()
|
||
|
|
.setRequiresCharging(false)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(CalendarImportWork::class, JOB_IMMEDIATE_CALENDAR_IMPORT)
|
||
|
|
.setInputData(data)
|
||
|
|
.setConstraints(constraints)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_IMMEDIATE_CALENDAR_IMPORT, ExistingWorkPolicy.KEEP, request)
|
||
|
|
|
||
|
|
return workManager.getJobInfo(request.id)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateFilesExportJob(files: Collection<OCFile>): LiveData<JobInfo?> {
|
||
|
|
val ids = files.map { it.fileId }.toLongArray()
|
||
|
|
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putLongArray(FilesExportWork.FILES_TO_DOWNLOAD, ids)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(FilesExportWork::class, JOB_IMMEDIATE_FILES_EXPORT)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_IMMEDIATE_FILES_EXPORT, ExistingWorkPolicy.APPEND_OR_REPLACE, request)
|
||
|
|
|
||
|
|
return workManager.getJobInfo(request.id)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateContactsBackup(user: User): LiveData<JobInfo?> {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(ContactsBackupWork.KEY_ACCOUNT, user.accountName)
|
||
|
|
.putBoolean(ContactsBackupWork.KEY_FORCE, true)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(ContactsBackupWork::class, JOB_IMMEDIATE_CONTACTS_BACKUP, user)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_IMMEDIATE_CONTACTS_BACKUP, ExistingWorkPolicy.KEEP, request)
|
||
|
|
return workManager.getJobInfo(request.id)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateCalendarBackup(user: User): LiveData<JobInfo?> {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(CalendarBackupWork.ACCOUNT, user.accountName)
|
||
|
|
.putBoolean(CalendarBackupWork.FORCE, true)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(CalendarBackupWork::class, JOB_IMMEDIATE_CALENDAR_BACKUP, user)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_IMMEDIATE_CALENDAR_BACKUP, ExistingWorkPolicy.KEEP, request)
|
||
|
|
return workManager.getJobInfo(request.id)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun schedulePeriodicCalendarBackup(user: User) {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(CalendarBackupWork.ACCOUNT, user.accountName)
|
||
|
|
.putBoolean(CalendarBackupWork.FORCE, true)
|
||
|
|
.build()
|
||
|
|
val request = periodicRequestBuilder(
|
||
|
|
jobClass = CalendarBackupWork::class,
|
||
|
|
jobName = JOB_PERIODIC_CALENDAR_BACKUP,
|
||
|
|
intervalMins = PERIODIC_BACKUP_INTERVAL_MINUTES,
|
||
|
|
user = user
|
||
|
|
).setInputData(data).build()
|
||
|
|
|
||
|
|
workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_CALENDAR_BACKUP, ExistingPeriodicWorkPolicy.KEEP, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun cancelPeriodicCalendarBackup(user: User) {
|
||
|
|
workManager.cancelJob(JOB_PERIODIC_CALENDAR_BACKUP, user)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun schedulePeriodicFilesSyncJob() {
|
||
|
|
val request = periodicRequestBuilder(
|
||
|
|
jobClass = FilesSyncWork::class,
|
||
|
|
jobName = JOB_PERIODIC_FILES_SYNC,
|
||
|
|
intervalMins = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES
|
||
|
|
).build()
|
||
|
|
workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_FILES_SYNC, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateFilesSyncJob(
|
||
|
|
overridePowerSaving: Boolean,
|
||
|
|
changedFiles: Array<String>
|
||
|
|
) {
|
||
|
|
val arguments = Data.Builder()
|
||
|
|
.putBoolean(FilesSyncWork.OVERRIDE_POWER_SAVING, overridePowerSaving)
|
||
|
|
.putStringArray(FilesSyncWork.CHANGED_FILES, changedFiles)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(
|
||
|
|
jobClass = FilesSyncWork::class,
|
||
|
|
jobName = JOB_IMMEDIATE_FILES_SYNC
|
||
|
|
)
|
||
|
|
.setInputData(arguments)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(JOB_IMMEDIATE_FILES_SYNC, ExistingWorkPolicy.APPEND, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun scheduleOfflineSync() {
|
||
|
|
val constrains = Constraints.Builder()
|
||
|
|
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = periodicRequestBuilder(OfflineSyncWork::class, JOB_PERIODIC_OFFLINE_SYNC)
|
||
|
|
.setConstraints(constrains)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_OFFLINE_SYNC, ExistingPeriodicWorkPolicy.KEEP, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun scheduleMediaFoldersDetectionJob() {
|
||
|
|
val request = periodicRequestBuilder(MediaFoldersDetectionWork::class, JOB_PERIODIC_MEDIA_FOLDER_DETECTION)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniquePeriodicWork(
|
||
|
|
JOB_PERIODIC_MEDIA_FOLDER_DETECTION,
|
||
|
|
ExistingPeriodicWorkPolicy.KEEP,
|
||
|
|
request
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startMediaFoldersDetectionJob() {
|
||
|
|
val request = oneTimeRequestBuilder(MediaFoldersDetectionWork::class, JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(
|
||
|
|
JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION,
|
||
|
|
ExistingWorkPolicy.KEEP,
|
||
|
|
request
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startNotificationJob(subject: String, signature: String) {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(NotificationWork.KEY_NOTIFICATION_SUBJECT, subject)
|
||
|
|
.putString(NotificationWork.KEY_NOTIFICATION_SIGNATURE, signature)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(NotificationWork::class, JOB_NOTIFICATION)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueue(request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startAccountRemovalJob(accountName: String, remoteWipe: Boolean) {
|
||
|
|
val data = Data.Builder()
|
||
|
|
.putString(AccountRemovalWork.ACCOUNT, accountName)
|
||
|
|
.putBoolean(AccountRemovalWork.REMOTE_WIPE, remoteWipe)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(AccountRemovalWork::class, JOB_ACCOUNT_REMOVAL)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueue(request)
|
||
|
|
}
|
||
|
|
|
||
|
|
private fun startFileUploadJobTag(user: User): String {
|
||
|
|
return JOB_FILES_UPLOAD + user.accountName
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun isStartFileUploadJobScheduled(user: User): Boolean {
|
||
|
|
return workManager.isWorkScheduled(startFileUploadJobTag(user))
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startFilesUploadJob(user: User) {
|
||
|
|
val data = workDataOf(FileUploadWorker.ACCOUNT to user.accountName)
|
||
|
|
|
||
|
|
val tag = startFileUploadJobTag(user)
|
||
|
|
|
||
|
|
val request = oneTimeRequestBuilder(FileUploadWorker::class, JOB_FILES_UPLOAD, user)
|
||
|
|
.addTag(tag)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(tag, 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.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() } }
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun cancelFilesUploadJob(user: User) {
|
||
|
|
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,
|
||
|
|
imagePaths: List<String>,
|
||
|
|
pdfPath: String
|
||
|
|
) {
|
||
|
|
val data = workDataOf(
|
||
|
|
GeneratePdfFromImagesWork.INPUT_IMAGE_FILE_PATHS to imagePaths.toTypedArray(),
|
||
|
|
GeneratePdfFromImagesWork.INPUT_OUTPUT_FILE_PATH to pdfPath,
|
||
|
|
GeneratePdfFromImagesWork.INPUT_UPLOAD_ACCOUNT to user.accountName,
|
||
|
|
GeneratePdfFromImagesWork.INPUT_UPLOAD_FOLDER to uploadFolder
|
||
|
|
)
|
||
|
|
val request = oneTimeRequestBuilder(GeneratePdfFromImagesWork::class, JOB_PDF_GENERATION)
|
||
|
|
.setInputData(data)
|
||
|
|
.build()
|
||
|
|
workManager.enqueue(request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun scheduleTestJob() {
|
||
|
|
val request = periodicRequestBuilder(TestJob::class, JOB_TEST)
|
||
|
|
.setInitialDelay(DEFAULT_IMMEDIATE_JOB_DELAY_SEC, TimeUnit.SECONDS)
|
||
|
|
.build()
|
||
|
|
workManager.enqueueUniquePeriodicWork(JOB_TEST, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startImmediateTestJob() {
|
||
|
|
val request = oneTimeRequestBuilder(TestJob::class, JOB_TEST)
|
||
|
|
.build()
|
||
|
|
workManager.enqueueUniqueWork(JOB_TEST, ExistingWorkPolicy.REPLACE, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun cancelTestJob() {
|
||
|
|
workManager.cancelAllWorkByTag(formatNameTag(JOB_TEST))
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun pruneJobs() {
|
||
|
|
workManager.pruneWork()
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun cancelAllJobs() {
|
||
|
|
workManager.cancelAllWorkByTag(TAG_ALL)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun schedulePeriodicHealthStatus() {
|
||
|
|
val request = periodicRequestBuilder(
|
||
|
|
jobClass = HealthStatusWork::class,
|
||
|
|
jobName = JOB_PERIODIC_HEALTH_STATUS,
|
||
|
|
intervalMins = PERIODIC_BACKUP_INTERVAL_MINUTES
|
||
|
|
).build()
|
||
|
|
|
||
|
|
workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_HEALTH_STATUS, ExistingPeriodicWorkPolicy.KEEP, request)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun startHealthStatus() {
|
||
|
|
val request = oneTimeRequestBuilder(HealthStatusWork::class, JOB_IMMEDIATE_HEALTH_STATUS)
|
||
|
|
.build()
|
||
|
|
|
||
|
|
workManager.enqueueUniqueWork(
|
||
|
|
JOB_IMMEDIATE_HEALTH_STATUS,
|
||
|
|
ExistingWorkPolicy.KEEP,
|
||
|
|
request
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|