123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- package com.owncloud.android.providers
- import android.provider.DocumentsContract
- import android.util.Log
- import androidx.documentfile.provider.DocumentFile
- import com.owncloud.android.AbstractOnServerIT
- import com.owncloud.android.R
- import com.owncloud.android.datamodel.OCFile.ROOT_PATH
- import com.owncloud.android.providers.DocumentsProviderUtils.assertExistsOnServer
- import com.owncloud.android.providers.DocumentsProviderUtils.assertListFilesEquals
- import com.owncloud.android.providers.DocumentsProviderUtils.assertReadEquals
- import com.owncloud.android.providers.DocumentsProviderUtils.assertRecentlyModified
- import com.owncloud.android.providers.DocumentsProviderUtils.assertRegularFile
- import com.owncloud.android.providers.DocumentsProviderUtils.assertRegularFolder
- import com.owncloud.android.providers.DocumentsProviderUtils.findFileBlocking
- import com.owncloud.android.providers.DocumentsProviderUtils.getOCFile
- import com.owncloud.android.providers.DocumentsProviderUtils.listFilesBlocking
- import com.owncloud.android.providers.DocumentsStorageProvider.DOCUMENTID_SEPARATOR
- import kotlinx.coroutines.runBlocking
- import net.bytebuddy.utility.RandomString
- import org.apache.commons.httpclient.HttpStatus
- import org.apache.commons.httpclient.methods.ByteArrayRequestEntity
- import org.apache.jackrabbit.webdav.client.methods.PutMethod
- import org.junit.After
- import org.junit.Assert.assertEquals
- import org.junit.Assert.assertFalse
- import org.junit.Assert.assertTrue
- import org.junit.Before
- import org.junit.Test
- import kotlin.random.Random
- private const val MAX_FILE_NAME_LENGTH = 225
- class DocumentsStorageProviderIT : AbstractOnServerIT() {
- private val context = targetContext
- private val contentResolver = context.contentResolver
- private val authority = context.getString(R.string.document_provider_authority)
- private val rootFileId = storageManager.getFileByEncryptedRemotePath(ROOT_PATH).fileId
- private val documentId = "${user.hashCode()}${DOCUMENTID_SEPARATOR}$rootFileId"
- private val uri = DocumentsContract.buildTreeDocumentUri(authority, documentId)
- private val rootDir get() = DocumentFile.fromTreeUri(context, uri)!!
- @Before
- fun before() {
- // DocumentsProvider#onCreate() is called when the application is started
- // which is *after* AbstractOnServerIT adds the accounts (when the app is freshly installed).
- // So we need to query our roots here to ensure that the internal storage map is initialized.
- contentResolver.query(DocumentsContract.buildRootsUri(authority), null, null, null)
- assertTrue("Storage root does not exist", rootDir.exists())
- assertTrue(rootDir.isDirectory)
- }
- /**
- * Delete all files in [rootDir] after each test.
- *
- * We can't use [AbstractOnServerIT.after] as this is only deleting remote files.
- */
- @After
- override fun after() = runBlocking {
- rootDir.listFilesBlocking(context).forEach {
- Log.e("TEST", "Deleting ${it.name}...")
- it.delete()
- }
- }
- @Test
- fun testCreateDeleteFiles() = runBlocking {
- // no files in root initially
- assertListFilesEquals(emptyList(), rootDir.listFilesBlocking(context))
- // create first file
- val name1 = RandomString.make()
- val type1 = "text/html"
- val file1 = rootDir.createFile(type1, name1)!!
- // check assumptions
- @Suppress("ForbiddenComment")
- file1.assertRegularFile(name1, 0L, null/* FIXME: type1 */, rootDir)
- file1.assertRecentlyModified()
- // file1 is found in root
- assertListFilesEquals(listOf(file1), rootDir.listFilesBlocking(context).toList())
- // file1 was uploaded
- val ocFile1 = file1.getOCFile(storageManager)!!
- assertExistsOnServer(client, ocFile1.remotePath, true)
- // create second long file with long file name
- val name2 = RandomString.make(MAX_FILE_NAME_LENGTH)
- val type2 = "application/octet-stream"
- val file2 = rootDir.createFile(type2, name2)!!
- // file2 was uploaded
- val ocFile2 = file2.getOCFile(storageManager)!!
- assertExistsOnServer(client, ocFile2.remotePath, true)
- // check assumptions
- file2.assertRegularFile(name2, 0L, type2, rootDir)
- file2.assertRecentlyModified()
- // both files get listed in root
- assertListFilesEquals(listOf(file1, file2), rootDir.listFiles().toList())
- // delete first file
- assertTrue(file1.delete())
- assertFalse(file1.exists())
- assertExistsOnServer(client, ocFile1.remotePath, false)
- // only second file gets listed in root
- assertListFilesEquals(listOf(file2), rootDir.listFiles().toList())
- // delete also second file
- assertTrue(file2.delete())
- assertFalse(file2.exists())
- assertExistsOnServer(client, ocFile2.remotePath, false)
- // no more files in root
- assertListFilesEquals(emptyList(), rootDir.listFilesBlocking(context))
- }
- @Test
- fun testReadWriteFiles() {
- // create random file
- val file1 = rootDir.createFile("application/octet-stream", RandomString.make())!!
- file1.assertRegularFile(size = 0L)
- // write random bytes to file
- @Suppress("MagicNumber")
- val dataSize = Random.nextInt(1, 99) * 1024
- val data1 = Random.nextBytes(dataSize)
- contentResolver.openOutputStream(file1.uri, "wt").use {
- it!!.write(data1)
- }
- // read back random bytes
- assertReadEquals(data1, contentResolver.openInputStream(file1.uri))
- // file size was updated correctly
- file1.assertRegularFile(size = data1.size.toLong())
- }
- @Test
- fun testCreateDeleteFolders() = runBlocking {
- // create a new folder
- val dirName1 = RandomString.make()
- val dir1 = rootDir.createDirectory(dirName1)!!
- dir1.assertRegularFolder(dirName1, rootDir)
- // FIXME about a minute gets lost somewhere after CFO sets the correct time
- @Suppress("MagicNumber")
- assertTrue(System.currentTimeMillis() - dir1.lastModified() < 60_000)
- // dir1.assertRecentlyModified()
- // ensure folder was uploaded to server
- val ocDir1 = dir1.getOCFile(storageManager)!!
- assertExistsOnServer(client, ocDir1.remotePath, true)
- // create file in folder
- val file1 = dir1.createFile("text/html", RandomString.make())!!
- file1.assertRegularFile(parent = dir1)
- val ocFile1 = file1.getOCFile(storageManager)!!
- assertExistsOnServer(client, ocFile1.remotePath, true)
- // we find the new file in the created folder and get it in the list
- assertEquals(file1.uri.toString(), dir1.findFileBlocking(context, file1.name!!)!!.uri.toString())
- assertListFilesEquals(listOf(file1), dir1.listFilesBlocking(context))
- // delete folder
- dir1.delete()
- assertFalse(dir1.exists())
- assertExistsOnServer(client, ocDir1.remotePath, false)
- // ensure file got deleted with it
- assertFalse(file1.exists())
- assertExistsOnServer(client, ocFile1.remotePath, false)
- }
- @Test
- fun testServerChangedFileContent() {
- // create random file
- val file1 = rootDir.createFile("text/plain", RandomString.make())!!
- file1.assertRegularFile(size = 0L)
- val createdETag = file1.getOCFile(storageManager)!!.etagOnServer
- assertTrue(createdETag.isNotEmpty())
- val content1 = "initial content".toByteArray()
- // write content bytes to file
- contentResolver.openOutputStream(file1.uri, "wt").use {
- it!!.write(content1)
- }
- // refresh
- while (file1.getOCFile(storageManager)!!.etagOnServer == createdETag) {
- shortSleep()
- rootDir.listFiles()
- }
- val remotePath = file1.getOCFile(storageManager)!!.remotePath
- val content2 = "new content".toByteArray()
- // modify content on server side
- val putMethod = PutMethod(client.getFilesDavUri(remotePath))
- putMethod.requestEntity = ByteArrayRequestEntity(content2)
- assertEquals(HttpStatus.SC_NO_CONTENT, client.executeMethod(putMethod))
- client.exhaustResponse(putMethod.responseBodyAsStream)
- putMethod.releaseConnection() // let the connection available for other methods
- // read back content bytes
- val bytes = contentResolver.openInputStream(file1.uri)?.readBytes() ?: ByteArray(0)
- assertEquals(String(content2), String(bytes))
- }
- }
|