Merge branch 'backend/filetree' into 'main'

feat: Filetree API

Closes #21

See merge request EmmaVandewalle/writand!23
This commit is contained in:
Emma Vandewalle 2024-07-22 19:58:43 +00:00
commit bfd19884a7
5 changed files with 221 additions and 18 deletions

View file

@ -3,10 +3,10 @@ package be.re.writand
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Test
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.

View file

@ -0,0 +1,86 @@
package be.re.writand.data.local
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.nio.file.Files
import java.nio.file.NotDirectoryException
import java.nio.file.Path
import java.nio.file.Paths
import java.util.PriorityQueue
import java.util.Queue
import kotlin.io.path.deleteExisting
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.name
class FileManagerLocal : IFileManager {
/**
* Checks if a directory is empty.
* Requires API 26!
*
* @param[path] The path of the directory to be checked.
* @return returns true if the directory is empty. If [path] points to a file,
* false is immediately returned.
*/
fun isDirectoryEmpty(path: Path): Boolean {
if (!path.isDirectory()) return false
return Files.list(path).count() == 0L
}
override suspend fun create(name: String, basePath: Path) {
if (!basePath.isDirectory()) throw NotDirectoryException(basePath.name)
val fullPath = Paths.get(basePath.toString(), name)
withContext(Dispatchers.IO) {
val res = fullPath.toFile().createNewFile()
if (!res) throw FileAlreadyExistsException(
fullPath.toFile(),
reason = "File already exists."
)
}
}
override suspend fun delete(path: Path) {
// path is a directory and is not empty, delete everything in this directory
if (path.isDirectory() && !isDirectoryEmpty(path)) {
val queue: Queue<Path> = PriorityQueue()
queue.add(path)
while (!queue.isEmpty()) {
val top = queue.poll()!!
if (top.isDirectory() && !isDirectoryEmpty(top)) {
// add all the elements of the current directory to the queue
for (dir in top.iterator()) {
queue.add(dir)
}
// add the directory back into the queue (top is not an empty directory)
queue.add(top)
} else {
// top is a file or an empty directory
top.deleteExisting()
}
}
} else {
path.deleteExisting()
}
}
override suspend fun move(from: Path, to: Path) {
withContext(Dispatchers.IO) {
Files.move(from, to)
}
}
override suspend fun rename(path: Path, newName: String) {
move(path, Paths.get(path.parent.toString(), newName))
}
override suspend fun walk(root: Path): FileTreeWalk {
return File(root.toUri()).walkTopDown()
}
}

View file

@ -0,0 +1,64 @@
package be.re.writand.data.local
import java.nio.file.Path
import java.nio.file.InvalidPathException
import java.nio.file.NotDirectoryException
import java.io.IOException
import java.io.IOError
/**
* An interface to handle the File trees.
* File is used to mention both real files and directories at the same time.
*/
interface IFileManager {
/**
* Create a new File.
* @param[name] the name of the new File.
* @param[basePath] the path where the new File should be created in.
* @throws InvalidPathException
* @throws FileAlreadyExistsException
* @throws NotDirectoryException
* @throws IOException
* @throws SecurityException
* */
suspend fun create(name: String, basePath: Path)
/**
* Delete a File.
* Symbolic links are followed.
* @param[path] the path pointing to the File to be deleted.
* @throws NoSuchFileException
*/
suspend fun delete(path: Path)
/**
* Move a File to a different location.
* If the File to be moved is a symbolic link, then the link itself is moved instead of the
* target of the link.
* @param[from] the start location.
* @param[to] the destination.
* @throws FileAlreadyExistsException
* @throws IOException
* @throws SecurityException
*/
suspend fun move(from: Path, to: Path)
/**
* Rename a file to a new name
* @param[path] the path to the File to rename
* @param[newName] the new name to be given to [path]
* @throws FileAlreadyExistsException
* @throws IOException
* @throws SecurityException
*/
suspend fun rename(path: Path, newName: String)
/**
* Walk the directory and build a File tree.
* @throws NullPointerException
* @throws IllegalArgumentException
* @throws IOError
* @throws SecurityException
*/
suspend fun walk(root: Path): FileTreeWalk
}

View file

@ -1,17 +0,0 @@
package be.re.writand
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View file

@ -0,0 +1,70 @@
package be.re.writand
import android.annotation.SuppressLint
import be.re.writand.data.local.FileManagerLocal
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
import org.mockito.Mockito.mockStatic
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import java.nio.file.FileSystem
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.spi.FileSystemProvider
import java.util.stream.Stream
import kotlin.io.path.isDirectory
/**
* The class containing all the tests for [FileManagerLocal].
*/
class FileManagerLocalTest {
private lateinit var fileManager: FileManagerLocal
@Mock
private lateinit var path: Path
@Mock
private lateinit var provider: FileSystemProvider
@Mock
private lateinit var fileSystem: FileSystem
@BeforeEach
fun setup() {
fileManager = FileManagerLocal()
MockitoAnnotations.openMocks(this)
}
companion object {
@SuppressLint("CheckResult")
@JvmStatic
@BeforeAll
fun setupStatic(): Unit {
mockStatic(Files::class.java)
}
}
@SuppressLint("CheckResult")
@Test
fun testIsDirectoryEmptySuccess() {
whenever(fileSystem.provider()).thenReturn(provider)
whenever(path.fileSystem).thenReturn(fileSystem)
whenever(Files.list(any())).thenReturn(Stream.empty())
whenever(path.isDirectory()).thenReturn(true)
assertTrue(fileManager.isDirectoryEmpty(path))
}
@Test
fun testIsDirectoryNotEmptySuccess() {
whenever(fileSystem.provider()).thenReturn(provider)
whenever(path.fileSystem).thenReturn(fileSystem)
whenever(Files.list(any())).thenReturn(Stream.of(path))
whenever(path.isDirectory()).thenReturn(true)
assertFalse(fileManager.isDirectoryEmpty(path))
}
}