Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New arch] Refresh folder #3709

Merged
merged 9 commits into from
Jul 7, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ val remoteDataSourceModule = module {

factory<RemoteAuthenticationDataSource> { OCRemoteAuthenticationDataSource(get()) }
factory<RemoteCapabilitiesDataSource> { OCRemoteCapabilitiesDataSource(get(), get()) }
factory<RemoteFileDataSource> { OCRemoteFileDataSource(get(), get()) }
factory<RemoteFileDataSource> { OCRemoteFileDataSource(get()) }
factory<RemoteOAuthDataSource> { RemoteOAuthDataSourceImpl(get(), get()) }
factory<RemoteServerInfoDataSource> { OCRemoteServerInfoDataSource(get(), get()) }
factory<RemoteShareDataSource> { OCRemoteShareDataSource(get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface LocalFileDataSource {
fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, remoteId: String)
fun getFileById(fileId: Long): OCFile?
fun getFileByRemotePath(remotePath: String, owner: String): OCFile?
fun getFileByRemoteId(remoteId: String): OCFile?
fun getFolderContent(folderId: Long): List<OCFile>
fun getSearchFolderContent(folderId: Long, search: String): List<OCFile>
fun getSearchAvailableOfflineFolderContent(folderId: Long, search: String): List<OCFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class OCLocalFileDataSource(
return null
}

override fun getFileByRemoteId(remoteId: String): OCFile? =
fileDao.getFileByRemoteId(remoteId)?.toModel()

override fun getFolderContent(folderId: Long): List<OCFile> =
fileDao.getFolderContent(folderId = folderId).map {
it.toModel()
Expand Down Expand Up @@ -113,15 +116,11 @@ class OCLocalFileDataSource(
)

override fun saveFilesInFolder(listOfFiles: List<OCFile>, folder: OCFile) {
// Insert first folder container
// TODO: If it is root, add 0 as parent Id
val folderId = fileDao.mergeRemoteAndLocalFile(folder.toEntity())

// Then, insert files inside
listOfFiles.forEach {
// Add parent id to each file
fileDao.mergeRemoteAndLocalFile(it.toEntity().apply { parentId = folderId })
}
fileDao.insertFilesInFolder(
folder = folder.toEntity(),
folderContent = listOfFiles.map { it.toEntity() }
)
}

override fun saveFile(file: OCFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ package com.owncloud.android.data.files.datasources.implementation
import com.owncloud.android.data.ClientManager
import com.owncloud.android.data.executeRemoteOperation
import com.owncloud.android.data.files.datasources.RemoteFileDataSource
import com.owncloud.android.data.files.datasources.mapper.RemoteFileMapper
import com.owncloud.android.domain.files.model.OCFile
import com.owncloud.android.lib.resources.files.RemoteFile

class OCRemoteFileDataSource(
private val clientManager: ClientManager,
private val remoteFileMapper: RemoteFileMapper
) : RemoteFileDataSource {

override fun checkPathExistence(
Expand Down Expand Up @@ -115,7 +114,7 @@ class OCRemoteFileDataSource(
clientManager.getFileService().readFile(
remotePath = remotePath
)
}.let { remoteFileMapper.toModel(it)!! }
}.toModel()

override fun refreshFolder(
remotePath: String
Expand All @@ -126,7 +125,7 @@ class OCRemoteFileDataSource(
remotePath = remotePath
)
}.let { listOfRemote ->
listOfRemote.map { remoteFile -> remoteFileMapper.toModel(remoteFile)!! }
listOfRemote.map { remoteFile -> remoteFile.toModel() }
}

override fun removeFile(
Expand All @@ -150,4 +149,22 @@ class OCRemoteFileDataSource(
isFolder = isFolder
)
}

private fun RemoteFile.toModel(): OCFile =
OCFile(
owner = owner,
remoteId = remoteId,
remotePath = remotePath,
length = if (isFolder) {
size
} else {
length
},
creationTimestamp = creationTimestamp,
modificationTimestamp = modifiedTimestamp,
mimeType = mimeType,
etag = etag,
permissions = permissions,
privateLink = privateLink
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.owncloud.android.domain.files.model.OCFile
import com.owncloud.android.domain.mappers.RemoteMapper
import com.owncloud.android.lib.resources.files.RemoteFile

@Deprecated("Used by legacy code. Remove as soon as the synchronize operations are done")
class RemoteFileMapper : RemoteMapper<OCFile, RemoteFile> {
override fun toModel(remote: RemoteFile?): OCFile? =
remote?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ abstract class FileDao {
remotePath: String
): OCFileEntity?

@Query(SELECT_FILE_WITH_REMOTE_ID)
abstract fun getFileByRemoteId(
remoteId: String
): OCFileEntity?

@Query(SELECT_FILTERED_FOLDER_CONTENT)
abstract fun getSearchFolderContent(
folderId: Long,
Expand Down Expand Up @@ -88,6 +93,21 @@ abstract class FileDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(ocFileEntity: OCFileEntity): Long

/**
* Make sure that the ids are set properly. We don't take care of conflicts and that stuff here.
*/
@Transaction
open fun insertFilesInFolder(
folder: OCFileEntity,
folderContent: List<OCFileEntity>,
) {
val folderId = insert(folder)

folderContent.forEach { fileToInsert ->
insert(fileToInsert.apply { parentId = folderId })
}
}

@Transaction
open fun mergeRemoteAndLocalFile(
ocFileEntity: OCFileEntity
Expand Down Expand Up @@ -248,6 +268,11 @@ abstract class FileDao {
"FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} " +
"WHERE id = :id"

private const val SELECT_FILE_WITH_REMOTE_ID =
"SELECT * " +
"FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} " +
"WHERE remoteId = :remoteId"

private const val SELECT_FILE_FROM_OWNER_WITH_REMOTE_PATH =
"SELECT * " +
"FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,72 @@ class OCFileRepository(
}

override fun refreshFolder(remotePath: String) {
remoteFileDataSource.refreshFolder(remotePath).also {
val currentSyncTime = System.currentTimeMillis()

// Retrieve remote folder data
val fetchFolderResult = remoteFileDataSource.refreshFolder(remotePath)
val remoteFolder = fetchFolderResult.first()
val remoteFolderContent = fetchFolderResult.drop(1)

// Check if the folder already exists in database.
val localFolderByRemotePath: OCFile? =
localFileDataSource.getFileByRemotePath(remotePath = remoteFolder.remotePath, owner = remoteFolder.owner)

// If folder doesn't exists in database, insert everything. Easy path
if (localFolderByRemotePath == null) {
localFileDataSource.saveFilesInFolder(
folder = remoteFolder,
listOfFiles = remoteFolderContent.map { it.apply { needsToUpdateThumbnail = !it.isFolder } }
)
} else {
// Keep the current local properties or we will miss relevant things.
remoteFolder.copyLocalPropertiesFrom(localFolderByRemotePath)

// Folder already exists in database, we need to update data
val localFolderContent = localFileDataSource.getFolderContent(folderId = localFolderByRemotePath.id!!)

val localFilesMap = localFolderContent.associateBy { localFile -> localFile.remoteId ?: localFile.remotePath }.toMutableMap()

// Final content for this folder, we will update the folder content all together
val folderContentUpdated = mutableListOf<OCFile>()

// Loop to sync every child
remoteFolderContent.forEach { remoteChild ->
remoteChild.lastSyncDateForProperties = currentSyncTime

// Let's try with remote path if the file does not have remote id yet
val localChildToSync = localFilesMap.remove(remoteChild.remoteId) ?: localFilesMap.remove(remoteChild.remotePath)

// If local child does not exists, just insert the new one.
if (localChildToSync == null) {
folderContentUpdated.add(
remoteChild.apply {
parentId = localFolderByRemotePath.id
needsToUpdateThumbnail = !remoteChild.isFolder
// remote eTag will not be set unless file CONTENTS are synchronized
etag = ""
})
} else {
// File exists in the database, we need to check several stuff.
folderContentUpdated.add(
remoteChild.apply {
copyLocalPropertiesFrom(localChildToSync)
// DO NOT update etag till contents are synced.
etag = localChildToSync.etag
needsToUpdateThumbnail =
!remoteChild.isFolder && remoteChild.modificationTimestamp != localChildToSync.modificationTimestamp
// FIXME: What about renames? Need to fix storage path
})
}
}
localFileDataSource.saveFilesInFolder(
folder = it.first(),
listOfFiles = it.drop(1)
folder = remoteFolder,
listOfFiles = folderContentUpdated
)
// Remaining items should be removed from the database and local storage. They do not exists in remote anymore.
removeFile(
listOfFilesToRemove = localFilesMap.map { it.value },
removeOnlyLocalCopy = false
)
}
}
Expand Down