Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,16 @@ interface FileSystemDao {
"""
)
fun getFileByPathAndFolder(localPath: String, syncedFolderId: String): FilesystemEntity?

@Query(
"""
SELECT COUNT(*) > 0
FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}
WHERE ${ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID} = :syncedFolderId
AND ${ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD} = 0
AND ${ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER} = 0
LIMIT 1
"""
)
suspend fun hasPendingFiles(syncedFolderId: String): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.documentscan.GeneratePDFUseCase
import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
import com.nextcloud.client.integrations.deck.DeckApi
import com.nextcloud.client.jobs.autoUpload.AutoUploadHelper
import com.nextcloud.client.jobs.autoUpload.AutoUploadWorker
import com.nextcloud.client.jobs.autoUpload.FileSystemRepository
import com.nextcloud.client.jobs.download.FileDownloadWorker
Expand Down Expand Up @@ -132,7 +133,10 @@ class BackgroundJobFactory @Inject constructor(
workerParameters,
SyncedFolderProvider(contentResolver, preferences, clock),
powerManagementService,
backgroundJobManager.get()
backgroundJobManager.get(),
AutoUploadHelper(
FileSystemRepository(dao = database.fileSystemDao(), uploadsStorageManager, context)
)
)

private fun createContactsBackupWork(context: Context, params: WorkerParameters): ContactsBackupWork =
Expand Down Expand Up @@ -178,7 +182,6 @@ class BackgroundJobFactory @Inject constructor(
connectivityService = connectivityService,
powerManagementService = powerManagementService,
syncedFolderProvider = syncedFolderProvider,
backgroundJobManager = backgroundJobManager.get(),
repository = FileSystemRepository(dao = database.fileSystemDao(), uploadsStorageManager, context),
viewThemeUtils = viewThemeUtils.get(),
localBroadcastManager = localBroadcastManager.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,7 @@ interface BackgroundJobManager {

fun startImmediateFilesExportJob(files: Collection<OCFile>): LiveData<JobInfo?>

fun startAutoUpload(
syncedFolder: SyncedFolder,
overridePowerSaving: Boolean = false,
contentUris: Array<String?> = arrayOf()
)
fun startAutoUpload(syncedFolder: SyncedFolder, overridePowerSaving: Boolean = false)

fun cancelTwoWaySyncJob()

Expand Down Expand Up @@ -163,7 +159,6 @@ interface BackgroundJobManager {
fun cancelAllJobs()
fun schedulePeriodicHealthStatus()
fun startHealthStatus()
fun isAutoUploadWorkerRunning(syncedFolderID: Long): Boolean
fun startOfflineOperations()
fun startPeriodicallyOfflineOperation()
fun scheduleInternal2WaySync(intervalMinutes: Long)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,6 @@ internal class BackgroundJobManagerImpl(
workManager.cancelJob(JOB_PERIODIC_CALENDAR_BACKUP, user)
}

override fun isAutoUploadWorkerRunning(syncedFolderID: Long): Boolean =
workManager.isWorkRunning(JOB_PERIODIC_FILES_SYNC + "_" + syncedFolderID) ||
workManager.isWorkRunning(JOB_IMMEDIATE_FILES_SYNC + "_" + syncedFolderID)

override fun startPeriodicallyOfflineOperation() {
val inputData = Data.Builder()
.putString(OfflineOperationsWorker.JOB_NAME, JOB_PERIODIC_OFFLINE_OPERATIONS)
Expand Down Expand Up @@ -478,16 +474,11 @@ internal class BackgroundJobManagerImpl(
)
}

override fun startAutoUpload(
syncedFolder: SyncedFolder,
overridePowerSaving: Boolean,
contentUris: Array<String?>
) {
override fun startAutoUpload(syncedFolder: SyncedFolder, overridePowerSaving: Boolean) {
val syncedFolderID = syncedFolder.id

val arguments = Data.Builder()
.putBoolean(AutoUploadWorker.OVERRIDE_POWER_SAVING, overridePowerSaving)
.putStringArray(AutoUploadWorker.CONTENT_URIS, contentUris)
.putLong(AutoUploadWorker.SYNCED_FOLDER_ID, syncedFolderID)
.build()

Expand All @@ -511,7 +502,7 @@ internal class BackgroundJobManagerImpl(

workManager.enqueueUniqueWork(
JOB_IMMEDIATE_FILES_SYNC + "_" + syncedFolderID,
ExistingWorkPolicy.APPEND,
ExistingWorkPolicy.KEEP,
request
)
}
Expand Down
31 changes: 27 additions & 4 deletions app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.autoUpload.AutoUploadHelper
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.utils.FilesSyncHelper
Expand All @@ -23,13 +24,14 @@ import kotlinx.coroutines.withContext
* It fires media detection worker and auto upload worker and finishes immediately.
*
*/
@Suppress("TooGenericExceptionCaught")
@Suppress("TooGenericExceptionCaught", "LongParameterList")
class ContentObserverWork(
context: Context,
private val params: WorkerParameters,
private val syncedFolderProvider: SyncedFolderProvider,
private val powerManagementService: PowerManagementService,
private val backgroundJobManager: BackgroundJobManager
private val backgroundJobManager: BackgroundJobManager,
private val autoUploadHelper: AutoUploadHelper
) : CoroutineWorker(context, params) {

companion object {
Expand Down Expand Up @@ -84,14 +86,35 @@ class ContentObserverWork(
val contentUris = params.triggeredContentUris.map { uri ->
// adds uri strings e.g. content://media/external/images/media/2281
uri.toString()
}.toTypedArray<String?>()
}.toTypedArray<String>()

Log_OC.d(TAG, "📄 Content uris detected")

try {
syncedFolderProvider.syncedFolders
.filter { it.isEnabled }
.forEach { folder ->
val inserted = if (contentUris.isEmpty()) {
Log_OC.d(TAG, "inserting all entries")
autoUploadHelper.insertEntries(folder)
true
} else {
Log_OC.d(TAG, "inserting changed entries")
autoUploadHelper.insertChangedEntries(folder, contentUris)
}

if (!inserted) {
Log_OC.w(
TAG,
"changed content uris not stored, fallback to insert all db entries to not lose files"
)
autoUploadHelper.insertEntries(folder)
}
}

FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(
syncedFolderProvider,
backgroundJobManager,
contentUris,
false
)
Log_OC.d(TAG, "✅ auto upload triggered successfully for ${contentUris.size} file(s).")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes

@Suppress("TooGenericExceptionCaught", "MagicNumber", "ReturnCount")
class AutoUploadHelper {
class AutoUploadHelper(private val repository: FileSystemRepository) {
companion object {
private const val TAG = "AutoUploadHelper"
private const val MAX_DEPTH = 100
}

fun insertEntries(folder: SyncedFolder, repository: FileSystemRepository) {
fun insertEntries(folder: SyncedFolder) {
when (folder.type) {
MediaFolderType.IMAGE -> {
repository.insertFromUri(MediaStore.Images.Media.INTERNAL_CONTENT_URI, folder)
Expand All @@ -43,7 +43,7 @@ class AutoUploadHelper {
}

else -> {
insertCustomFolderIntoDB(folder, repository)
insertCustomFolderIntoDB(folder)
}
}
}
Expand All @@ -59,11 +59,7 @@ class AutoUploadHelper {
* {@link ContentObserverWork##checkAndTriggerAutoUpload()}.
* @return {@code true} if all changed content URIs were successfully stored; {@code false} otherwise.
*/
fun insertChangedEntries(
syncedFolder: SyncedFolder,
contentUris: Array<String>?,
repository: FileSystemRepository
): Boolean {
fun insertChangedEntries(syncedFolder: SyncedFolder, contentUris: Array<String>?): Boolean {
contentUris?.forEach { uriString ->
try {
val uri = uriString.toUri()
Expand All @@ -79,7 +75,7 @@ class AutoUploadHelper {
return true
}

fun insertCustomFolderIntoDB(folder: SyncedFolder, repository: FileSystemRepository): Int {
fun insertCustomFolderIntoDB(folder: SyncedFolder): Int {
val path = Paths.get(folder.localPath)

if (!Files.exists(path)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ import com.owncloud.android.operations.UploadFileOperation
import com.owncloud.android.ui.activity.SettingsActivity
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.coroutines.cancellation.CancellationException

@Suppress("LongParameterList", "TooManyFunctions", "TooGenericExceptionCaught")
class AutoUploadWorker(
Expand All @@ -55,7 +58,6 @@ class AutoUploadWorker(
private val connectivityService: ConnectivityService,
private val powerManagementService: PowerManagementService,
private val syncedFolderProvider: SyncedFolderProvider,
private val backgroundJobManager: BackgroundJobManager,
private val repository: FileSystemRepository,
val viewThemeUtils: ViewThemeUtils,
localBroadcastManager: LocalBroadcastManager
Expand All @@ -64,12 +66,10 @@ class AutoUploadWorker(
companion object {
const val TAG = "🔄📤" + "AutoUpload"
const val OVERRIDE_POWER_SAVING = "overridePowerSaving"
const val CONTENT_URIS = "content_uris"
const val SYNCED_FOLDER_ID = "syncedFolderId"
const val NOTIFICATION_ID = 266
}

private val helper = AutoUploadHelper()
private val syncFolderHelper = SyncFolderHelper(context)
private val fileUploadBroadcastManager = FileUploadBroadcastManager(localBroadcastManager)
private lateinit var syncedFolder: SyncedFolder
Expand All @@ -84,20 +84,14 @@ class AutoUploadWorker(

Log_OC.d(TAG, syncedFolder.getLog())

/**
* Receives from [com.nextcloud.client.jobs.ContentObserverWork.checkAndTriggerAutoUpload]
*/
val contentUris = inputData.getStringArray(CONTENT_URIS)

if (canExitEarly(contentUris, syncFolderId)) {
return Result.retry()
if (canExitEarly(syncFolderId)) {
return Result.success()
}

if (powerManagementService.isPowerSavingEnabled) {
Log_OC.w(TAG, "power saving mode enabled")
}

collectFileChangesFromContentObserverWork(contentUris)
uploadFiles(syncedFolder)

// only update last scan time after uploading files
Expand All @@ -106,6 +100,9 @@ class AutoUploadWorker(

Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} completed")
Result.success()
} catch (e: CancellationException) {
Log_OC.w(TAG, "⚠️ Job cancelled")
throw e
} catch (e: Exception) {
Log_OC.e(TAG, "❌ failed: ${e.message}")
Result.failure()
Expand Down Expand Up @@ -172,7 +169,7 @@ class AutoUploadWorker(
}

@Suppress("ReturnCount")
private fun canExitEarly(contentUris: Array<String>?, syncedFolderID: Long): Boolean {
private suspend fun canExitEarly(syncedFolderID: Long): Boolean {
val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
if ((powerManagementService.isPowerSavingEnabled && !overridePowerSaving)) {
Log_OC.w(TAG, "⚡ Skipping: device is in power saving mode")
Expand All @@ -184,16 +181,17 @@ class AutoUploadWorker(
return true
}

if (backgroundJobManager.isAutoUploadWorkerRunning(syncedFolderID)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't interfere, work manager already handling it via policy.

Log_OC.w(TAG, "🚧 another worker is already running for $syncedFolderID")
return true
val hasPendingFiles = repository.hasPendingFiles(syncedFolder)
if (hasPendingFiles) {
Log_OC.d(TAG, "pending files found, starting...")
return false
}

val totalScanInterval = syncedFolder.getTotalScanInterval(connectivityService, powerManagementService)
val currentTime = System.currentTimeMillis()
val passedScanInterval = totalScanInterval <= currentTime

if (!passedScanInterval && contentUris.isNullOrEmpty() && !overridePowerSaving) {
if (!passedScanInterval && !overridePowerSaving) {
Log_OC.w(
TAG,
"skipped since started before scan interval and nothing todo: " + syncedFolder.localPath
Expand All @@ -206,35 +204,6 @@ class AutoUploadWorker(
return false
}

/**
* Instead of scanning the entire local folder, optional content URIs can be passed to the worker
* to detect only the relevant changes.
*/
@Suppress("MagicNumber", "TooGenericExceptionCaught")
private suspend fun collectFileChangesFromContentObserverWork(contentUris: Array<String>?) = try {
Log_OC.d(TAG, "collecting file changes")

withContext(Dispatchers.IO) {
if (contentUris.isNullOrEmpty()) {
Log_OC.d(TAG, "inserting all entries")
helper.insertEntries(syncedFolder, repository)
} else {
Log_OC.d(TAG, "inserting changed entries")
val isContentUrisStored = helper.insertChangedEntries(syncedFolder, contentUris, repository)
if (!isContentUrisStored) {
Log_OC.w(
TAG,
"changed content uris not stored, fallback to insert all db entries to not lose files"
)

helper.insertEntries(syncedFolder, repository)
}
}
}
} catch (e: Exception) {
Log_OC.d(TAG, "Exception collectFileChangesFromContentObserverWork: $e")
}

private fun getUserOrReturn(syncedFolder: SyncedFolder): User? {
val optionalUser = userAccountManager.getUser(syncedFolder.account)
if (!optionalUser.isPresent) {
Expand Down Expand Up @@ -279,7 +248,7 @@ class AutoUploadWorker(

var lastId = 0

while (true) {
while (isActive) {
val filePathsWithIds = repository.getFilePathsWithIds(syncedFolder, lastId)

if (filePathsWithIds.isEmpty()) {
Expand All @@ -289,6 +258,8 @@ class AutoUploadWorker(
Log_OC.d(TAG, "started, processing batch: lastId=$lastId, count=${filePathsWithIds.size}")

filePathsWithIds.forEachIndexed { batchIndex, (path, id) ->
ensureActive()

val file = File(path)
val localPath = file.absolutePath
val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class FileSystemRepository(
dao.delete(entity)
}

suspend fun hasPendingFiles(syncedFolder: SyncedFolder): Boolean = dao.hasPendingFiles(syncedFolder.id.toString())

suspend fun deleteByLocalPathAndId(path: String, id: Int) {
dao.deleteByLocalPathAndId(path, id)
}
Expand Down Expand Up @@ -91,7 +93,7 @@ class FileSystemRepository(
dao.markFileAsUploaded(localPath, syncedFolderIdStr)
Log_OC.d(TAG, "Marked file as uploaded: $localPath for syncedFolderId=$syncedFolderIdStr")
} catch (e: Exception) {
Log_OC.e(TAG, "Error marking file as uploaded: ${e.message}", e)
Log_OC.e(TAG, "markFileAsHandled(): ${e.message}", e)
}
}

Expand Down
1 change: 0 additions & 1 deletion app/src/main/java/com/owncloud/android/MainApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ public void disableDocumentsStorageProvider() {
if (preferences.startAutoUploadOnStart()) {
FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider,
backgroundJobManager,
new String[]{},
false);
preferences.setLastAutoUploadOnStartTime(System.currentTimeMillis());
}
Expand Down
Loading
Loading