Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.nextcloud.client.database.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
Expand All @@ -19,6 +20,9 @@ interface FileSystemDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrReplace(filesystemEntity: FilesystemEntity)

@Delete
fun delete(entity: FilesystemEntity)

@Query(
"""
DELETE FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface UploadDao {
"WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName " +
"AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} = :remotePath"
)
fun deleteByAccountAndRemotePath(remotePath: String, accountName: String)
fun deleteByRemotePathAndAccountName(remotePath: String, accountName: String)

@Query(
"SELECT * FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME +
Expand All @@ -51,7 +51,7 @@ interface UploadDao {
)
fun getUploadById(id: Long, accountName: String): UploadEntity?

@Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrReplace(entity: UploadEntity): Long

@Query(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class BackgroundJobFactory @Inject constructor(
powerManagementService = powerManagementService,
syncedFolderProvider = syncedFolderProvider,
backgroundJobManager = backgroundJobManager.get(),
repository = FileSystemRepository(dao = database.fileSystemDao(), context),
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 @@ -9,8 +9,6 @@ package com.nextcloud.client.jobs.autoUpload

import android.app.Notification
import android.content.Context
import android.content.res.Resources
import androidx.exifinterface.media.ExifInterface
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
Expand All @@ -25,13 +23,11 @@ import com.nextcloud.client.jobs.upload.FileUploadBroadcastManager
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.preferences.SubFolderRule
import com.nextcloud.utils.extensions.isNonRetryable
import com.nextcloud.utils.extensions.updateStatus
import com.owncloud.android.R
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.MediaFolderType
import com.owncloud.android.datamodel.SyncedFolder
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.UploadsStorageManager
Expand All @@ -44,16 +40,10 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.UploadFileOperation
import com.owncloud.android.ui.activity.SettingsActivity
import com.owncloud.android.utils.FileStorageUtils
import com.owncloud.android.utils.MimeType
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.text.ParsePosition
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone

@Suppress("LongParameterList", "TooManyFunctions", "TooGenericExceptionCaught")
class AutoUploadWorker(
Expand All @@ -79,6 +69,7 @@ class AutoUploadWorker(
}

private val helper = AutoUploadHelper()
private val syncFolderHelper = SyncFolderHelper(context)
private val fileUploadBroadcastManager = FileUploadBroadcastManager(localBroadcastManager)
private lateinit var syncedFolder: SyncedFolder
private val notificationManager = AutoUploadNotificationManager(context, viewThemeUtils, NOTIFICATION_ID)
Expand All @@ -102,7 +93,11 @@ class AutoUploadWorker(
collectFileChangesFromContentObserverWork(contentUris)
uploadFiles(syncedFolder)

Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} finished checking files.")
// only update last scan time after uploading files
syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
syncedFolderProvider.updateSyncFolder(syncedFolder)

Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} completed")
Result.success()
} catch (e: Exception) {
Log_OC.e(TAG, "❌ failed: ${e.message}")
Expand Down Expand Up @@ -226,20 +221,11 @@ class AutoUploadWorker(
helper.insertEntries(syncedFolder, repository)
}
}
syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
syncedFolderProvider.updateSyncFolder(syncedFolder)
}
} catch (e: Exception) {
Log_OC.d(TAG, "Exception collectFileChangesFromContentObserverWork: $e")
}

private fun prepareDateFormat(): SimpleDateFormat {
val currentLocale = context.resources.configuration.locales[0]
return SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply {
timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
}
}

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

@Suppress("LongMethod", "DEPRECATION", "TooGenericExceptionCaught")
private suspend fun uploadFiles(syncedFolder: SyncedFolder) = withContext(Dispatchers.IO) {
val dateFormat = prepareDateFormat()
val user = getUserOrReturn(syncedFolder) ?: return@withContext
val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context)
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getClientFor(ocAccount, context)
val lightVersion = context.resources.getBoolean(R.bool.syncedFolder_light)
val currentLocale = context.resources.configuration.locales[0]

trySetForeground()
updateNotification()
Expand All @@ -299,14 +282,7 @@ class AutoUploadWorker(
filePathsWithIds.forEachIndexed { batchIndex, (path, id) ->
val file = File(path)
val localPath = file.absolutePath
val remotePath = getRemotePath(
file,
syncedFolder,
dateFormat,
lightVersion,
context.resources,
currentLocale
)
val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)

try {
val entityResult = getEntityResult(user, localPath, remotePath)
Expand Down Expand Up @@ -337,7 +313,6 @@ class AutoUploadWorker(

val result = operation.execute(client)
fileUploadBroadcastManager.sendStarted(operation, context)
uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess)

UploadErrorNotificationManager.handleResult(
context,
Expand Down Expand Up @@ -471,79 +446,6 @@ class AutoUploadWorker(
FileDataStorageManager(user, context.contentResolver)
)

private fun getRemotePath(
file: File,
syncedFolder: SyncedFolder,
sFormatter: SimpleDateFormat,
lightVersion: Boolean,
resources: Resources,
currentLocale: Locale
): String {
val lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter)

val (remoteFolder, useSubfolders, subFolderRule) = if (lightVersion) {
Triple(
resources.getString(R.string.syncedFolder_remote_folder),
resources.getBoolean(R.bool.syncedFolder_light_use_subfolders),
SubFolderRule.YEAR_MONTH
)
} else {
Triple(
syncedFolder.remotePath,
syncedFolder.isSubfolderByDate,
syncedFolder.subfolderRule
)
}

return FileStorageUtils.getInstantUploadFilePath(
file,
currentLocale,
remoteFolder,
syncedFolder.localPath,
lastModificationTime,
useSubfolders,
subFolderRule
)
}

private fun hasExif(file: File): Boolean {
val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath)
return MimeType.JPEG.equals(mimeType, ignoreCase = true) || MimeType.TIFF.equals(mimeType, ignoreCase = true)
}

@Suppress("NestedBlockDepth")
private fun calculateLastModificationTime(
file: File,
syncedFolder: SyncedFolder,
formatter: SimpleDateFormat
): Long {
var lastModificationTime = file.lastModified()
if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) {
Log_OC.d(TAG, "calculateLastModificationTime exif found")

@Suppress("TooGenericExceptionCaught")
try {
val exifInterface = ExifInterface(file.absolutePath)
val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)
if (!exifDate.isNullOrBlank()) {
val pos = ParsePosition(0)
val dateTime = formatter.parse(exifDate, pos)
if (dateTime != null) {
lastModificationTime = dateTime.time
Log_OC.w(TAG, "calculateLastModificationTime calculatedTime is: $lastModificationTime")
} else {
Log_OC.w(TAG, "calculateLastModificationTime dateTime is empty")
}
} else {
Log_OC.w(TAG, "calculateLastModificationTime exifDate is empty")
}
} catch (e: Exception) {
Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage)
}
}
return lastModificationTime
}

private fun sendUploadFinishEvent(operation: UploadFileOperation, result: RemoteOperationResult<*>) {
fileUploadBroadcastManager.sendFinished(
operation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,37 @@ import com.nextcloud.client.database.entity.FilesystemEntity
import com.nextcloud.utils.extensions.shouldSkipFile
import com.nextcloud.utils.extensions.toFile
import com.owncloud.android.datamodel.SyncedFolder
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.utils.SyncedFolderUtils
import java.io.File
import java.util.zip.CRC32

@Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "MagicNumber", "ReturnCount")
class FileSystemRepository(private val dao: FileSystemDao, private val context: Context) {
class FileSystemRepository(
private val dao: FileSystemDao,
private val uploadsStorageManager: UploadsStorageManager,
private val context: Context
) {
private val syncFolderHelper = SyncFolderHelper(context)

companion object {
private const val TAG = "FilesystemRepository"
const val BATCH_SIZE = 50
}

fun deleteAutoUploadAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) {
Log_OC.d(TAG, "deleting auto upload entity and upload entity")

val file = File(localPath)
val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)
uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName(
remotePath = remotePath,
accountName = syncedFolder.account
)
dao.delete(entity)
}

suspend fun deleteByLocalPathAndId(path: String, id: Int) {
dao.deleteByLocalPathAndId(path, id)
}
Expand All @@ -39,20 +57,23 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context:
val entities = dao.getAutoUploadFilesEntities(syncedFolderId, BATCH_SIZE, lastId)
val filtered = mutableListOf<Pair<String, Int>>()

entities.forEach {
it.localPath?.let { path ->
entities.forEach { entity ->
entity.localPath?.let { path ->
val file = File(path)
if (!file.exists()) {
Log_OC.w(TAG, "Ignoring file for upload (doesn't exist): $path")
deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
} else if (!SyncedFolderUtils.isQualifiedFolder(file.parent)) {
Log_OC.w(TAG, "Ignoring file for upload (unqualified folder): $path")
deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
} else if (!SyncedFolderUtils.isFileNameQualifiedForAutoUpload(file.name)) {
Log_OC.w(TAG, "Ignoring file for upload (unqualified file): $path")
deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
} else {
Log_OC.d(TAG, "Adding path to upload: $path")

if (it.id != null) {
filtered.add(path to it.id)
if (entity.id != null) {
filtered.add(path to entity.id)
} else {
Log_OC.w(TAG, "cant adding path to upload, id is null")
}
Expand Down Expand Up @@ -160,22 +181,21 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context:
}

val entity = dao.getFileByPathAndFolder(localPath, syncedFolder.id.toString())
val fileSentForUpload = (entity != null && entity.fileSentForUpload == 1)
if (fileSentForUpload) {
Log_OC.d(TAG, "File was sent for upload, checking if it changed...")
}

val fileModified = (lastModified ?: file.lastModified())
Comment thread
alperozturk96 marked this conversation as resolved.
if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) {
val hasNotChanged = entity?.fileModified == fileModified
val fileSentForUpload = entity?.fileSentForUpload == 1

if (hasNotChanged && fileSentForUpload) {
Log_OC.d(TAG, "File hasn't changed since last scan. skipping: $localPath")
return
}

if (fileSentForUpload) {
Log_OC.d(TAG, "File was sent for upload before but has changed, will re-upload: $localPath")
if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) {
return
}

val crc = getFileChecksum(file)

val newEntity = FilesystemEntity(
id = entity?.id,
localPath = localPath,
Expand Down
Loading
Loading