Skip to content

Commit

Permalink
refactor parts of FileDataStorageManager
Browse files Browse the repository at this point in the history
  • Loading branch information
theScrabi committed Sep 9, 2020
1 parent eba6d85 commit 8c1cd6f
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 137 deletions.
11 changes: 10 additions & 1 deletion .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.owncloud.android.datamodel

import android.content.ContentResolver
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.owncloud.android.testutil.OC_ACCOUNT
import io.mockk.mockk
import junit.framework.Assert.assertEquals
import org.junit.Test

class FileDataStorageManagerTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val contentResolver = mockk<ContentResolver>()

@Test
fun testGetParentDirectory() {
val manager = FileDataStorageManager(context, OC_ACCOUNT, contentResolver)
assertEquals("/some/path/with/", manager.getParentPath("/some/path/with/tailing/"))
assertEquals("/some/path/with/no/", manager.getParentPath("/some/path/with/no/tailing"))
assertEquals("", manager.getParentPath("/"))
assertEquals("", manager.getParentPath(""))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,70 +40,15 @@ import android.os.Build
import android.os.FileUriExposedException
import android.os.RemoteException
import android.provider.MediaStore
import android.util.Log
import androidx.core.content.FileProvider
import androidx.core.util.Pair
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.authentication.AccountUtils
import com.owncloud.android.datamodel.OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE
import com.owncloud.android.datamodel.OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT
import com.owncloud.android.datamodel.OCFile.AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE
import com.owncloud.android.datamodel.OCFile.AvailableOfflineStatus.fromValue
import com.owncloud.android.datamodel.OCFile.AvailableOfflineStatus.*
import com.owncloud.android.datamodel.OCFile.ROOT_PATH
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_CORE_POLLINTERVAL
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_DAV_CHUNKING_VERSION
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_FILES_BIGFILECHUNKING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_FILES_UNDELETE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_FILES_VERSIONING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_API_ENABLED
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_INCOMING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_OUTGOING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_ENABLED
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_MULTIPLE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_READ_ONLY
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_READ_WRITE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED_UPLOAD_ONLY
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_SUPPORTS_UPLOAD_ONLY
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_UPLOAD
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_SHARING_RESHARING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_EDITION
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MAYOR
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MICRO
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_MINOR
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CAPABILITIES_VERSION_STRING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CONTENT_URI
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CONTENT_URI_CAPABILITIES
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CONTENT_URI_DIR
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.CONTENT_URI_FILE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_ACCOUNT_OWNER
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_CONTENT_LENGTH
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_CONTENT_TYPE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_CREATION
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_ETAG
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_ETAG_IN_CONFLICT
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_IS_DOWNLOADING
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_KEEP_IN_SYNC
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_LAST_SYNC_DATE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_MODIFIED
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_NAME
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_PARENT
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_PATH
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_PERMISSIONS
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_PRIVATE_LINK
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_REMOTE_ID
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_SHARED_VIA_LINK
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_SHARED_WITH_SHAREE
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_STORAGE_PATH
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_TREE_ETAG
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.FILE_UPDATE_THUMBNAIL
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta._ID
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta.*
import com.owncloud.android.domain.capabilities.model.CapabilityBooleanType
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.lib.resources.status.RemoteCapability
Expand Down Expand Up @@ -1160,18 +1105,26 @@ class FileDataStorageManager {
}
}

/**
* Resolves previously set conflicts.
* safe blanket: sync'ing a not in-conflict file will clean wrong conflict markers in ancestors
*/
fun resolveConflict(file: OCFile) {
saveConflict(file, null)
}

fun saveConflict(file: OCFile, eTagInConflictFromParameter: String?) {
var eTagInConflict = eTagInConflictFromParameter
if (!file.isDown) {
eTagInConflict = null
}
val cv = ContentValues()
cv.put(FILE_ETAG_IN_CONFLICT, eTagInConflict)
val contentValues = ContentValues()
contentValues.put(FILE_ETAG_IN_CONFLICT, eTagInConflict)
val updated =
try {
performUpdate(
uri = CONTENT_URI_FILE,
contentValues = cv,
contentValues = contentValues,
where = "$_ID=?",
selectionArgs = arrayOf(file.fileId.toString())
)
Expand All @@ -1181,95 +1134,103 @@ class FileDataStorageManager {
}

Timber.d("Number of files updated with CONFLICT: $updated")

if (updated > 0) {
if (eTagInConflict != null) {
/// set conflict in all ancestor folders
updateConflictInSubFolder(file, contentValues)
} else {
updateConflictInAncestorFolder(file, contentValues)
}
}
}

var parentId = file.parentId
val ancestorIds = HashSet<String>()
while (parentId != ROOT_PARENT_ID.toLong()) {
ancestorIds.add(parentId.toString())
parentId = getFileById(parentId)!!.parentId
}
private fun updateConflictInSubFolder(file: OCFile, contentValues: ContentValues?) {
// set conflict in all ancestor folders

if (ancestorIds.size > 0) {
val whereBuffer = StringBuffer()
whereBuffer.append(_ID).append(" IN (")
for (i in 0 until ancestorIds.size - 1) {
whereBuffer.append("?,")
}
whereBuffer.append("?")
whereBuffer.append(")")

try {
performUpdate(
uri = CONTENT_URI_FILE,
contentValues = cv,
where = whereBuffer.toString(),
selectionArgs = ancestorIds.toTypedArray()
)
} catch (e: RemoteException) {
Timber.e(e, "Failed saving conflict in database ${e.message}")
}
} // else file is ROOT folder, no parent to set in conflict

} else {
/// update conflict in ancestor folders
// (not directly unset; maybe there are more conflicts below them)
var parentPath = file.remotePath
if (parentPath.endsWith(File.separator)) {
parentPath = parentPath.substring(0, parentPath.length - 1)
}
parentPath = parentPath.substring(0, parentPath.lastIndexOf(File.separator) + 1)
var parentId = file.parentId
val ancestorIds = HashSet<String>()
while (parentId != ROOT_PARENT_ID.toLong()) {
ancestorIds.add(parentId.toString())
parentId = getFileById(parentId)!!.parentId
}

Timber.d("checking parents to remove conflict; STARTING with $parentPath")
while (parentPath.isNotEmpty()) {
if (ancestorIds.size > 0) {
val whereBuffer = StringBuffer()
whereBuffer.append(_ID).append(" IN (")
for (i in 0 until ancestorIds.size - 1) {
whereBuffer.append("?,")
}
whereBuffer.append("?)")

val whereForDescendantsInConflict = FILE_ETAG_IN_CONFLICT + " IS NOT NULL AND " +
FILE_CONTENT_TYPE + " != 'DIR' AND " +
FILE_ACCOUNT_OWNER + " = ? AND " +
FILE_PATH + " LIKE ?"
val descendantsInConflict: Cursor? =
try {
performQuery(
uri = CONTENT_URI_FILE,
projection = arrayOf(_ID),
selection = whereForDescendantsInConflict,
selectionArgs = arrayOf(account.name, "$parentPath%"),
sortOrder = null
)
} catch (e: RemoteException) {
Timber.e(e, "Failed querying for descendants in conflict ${e.message}")
null
}
try {
performUpdate(
uri = CONTENT_URI_FILE,
contentValues = contentValues,
where = whereBuffer.toString(),
selectionArgs = ancestorIds.toTypedArray()
)
} catch (e: RemoteException) {
Timber.e(e, "Failed saving conflict in database ${e.message}")
}
} // else file is ROOT folder, no parent to set in conflict
}

if (descendantsInConflict == null || descendantsInConflict.count == 0) {
Timber.d("NO MORE conflicts in $parentPath")
private fun updateConflictInAncestorFolder(file: OCFile, contentValues: ContentValues?) {
/// update conflict in ancestor folders
// (not directly unset; maybe there are more conflicts below them)
val parentPath = getParentPath(file.storagePath)

try {
performUpdate(
uri = CONTENT_URI_FILE,
contentValues = cv,
where = "$FILE_ACCOUNT_OWNER=? AND $FILE_PATH=?",
selectionArgs = arrayOf(account.name, parentPath)
)
} catch (e: RemoteException) {
Timber.e(e, "Failed saving conflict in database ${e.message}")
}
Timber.d("checking parents to remove conflict; STARTING with $parentPath")
while (parentPath.isNotEmpty()) {

} else {
Timber.d("STILL ${descendantsInConflict.count} in $parentPath")
}
val whereForDescendantsInConflict = FILE_ETAG_IN_CONFLICT + " IS NOT NULL AND " +
FILE_CONTENT_TYPE + " != 'DIR' AND " +
FILE_ACCOUNT_OWNER + " = ? AND " +
FILE_PATH + " LIKE ?"
val descendantsInConflict: Cursor? =
try {
performQuery(
uri = CONTENT_URI_FILE,
projection = arrayOf(_ID),
selection = whereForDescendantsInConflict,
selectionArgs = arrayOf(account.name, "$parentPath%"),
sortOrder = null
)
} catch (e: RemoteException) {
Timber.e(e, "Failed querying for descendants in conflict ${e.message}")
null
}

descendantsInConflict?.close()
if (descendantsInConflict == null || descendantsInConflict.count == -1) {
Timber.d("NO MORE conflicts in $parentPath")

parentPath = parentPath.substring(0, parentPath.length - 1) // trim last /
parentPath = parentPath.substring(0, parentPath.lastIndexOf(File.separator) + 1)
Timber.d("checking parents to remove conflict; NEXT $parentPath")
try {
performUpdate(
uri = CONTENT_URI_FILE,
contentValues = contentValues,
where = "$FILE_ACCOUNT_OWNER=? AND $FILE_PATH=?",
selectionArgs = arrayOf(account.name, parentPath)
)
} catch (e: RemoteException) {
Timber.e(e, "Failed saving conflict in database ${e.message}")
}

} else {
Timber.d("STILL ${descendantsInConflict.count} in $parentPath")
}

descendantsInConflict?.close()

val parentOfParentPath = getParentPath(parentPath)
Timber.d("checking parents to remove conflict; NEXT $parentOfParentPath")
}
}

fun getParentPath(childPath: String): String {
var parentPath = childPath
if (parentPath.endsWith(File.separator)) {
parentPath = parentPath.substring(0, parentPath.length - 1)
}
return parentPath.substring(0, parentPath.lastIndexOf(File.separator) + 1)
}

fun saveCapabilities(capability: RemoteCapability): RemoteCapability {
Expand Down

0 comments on commit 8c1cd6f

Please sign in to comment.