From b1fa2baf76ed16ce9f679c803628c766ec36f1e2 Mon Sep 17 00:00:00 2001 From: Emma Vandewalle Date: Mon, 22 Jul 2024 19:58:43 +0000 Subject: [PATCH] feat: Filetree API --- .../be/re/writand/ExampleInstrumentedTest.kt | 2 +- .../re/writand/data/local/FileManagerLocal.kt | 86 +++++++++++++++++++ .../be/re/writand/data/local/IFileManager.kt | 64 ++++++++++++++ .../java/be/re/writand/ExampleUnitTest.kt | 17 ---- .../be/re/writand/FileManagerLocalTest.kt | 70 +++++++++++++++ 5 files changed, 221 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/be/re/writand/data/local/FileManagerLocal.kt create mode 100644 app/src/main/java/be/re/writand/data/local/IFileManager.kt delete mode 100644 app/src/test/java/be/re/writand/ExampleUnitTest.kt create mode 100644 app/src/test/java/be/re/writand/FileManagerLocalTest.kt diff --git a/app/src/androidTest/java/be/re/writand/ExampleInstrumentedTest.kt b/app/src/androidTest/java/be/re/writand/ExampleInstrumentedTest.kt index 067f2e0..f38f983 100644 --- a/app/src/androidTest/java/be/re/writand/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/be/re/writand/ExampleInstrumentedTest.kt @@ -3,10 +3,10 @@ package be.re.writand import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* +import org.junit.Test /** * Instrumented test, which will execute on an Android device. diff --git a/app/src/main/java/be/re/writand/data/local/FileManagerLocal.kt b/app/src/main/java/be/re/writand/data/local/FileManagerLocal.kt new file mode 100644 index 0000000..1905324 --- /dev/null +++ b/app/src/main/java/be/re/writand/data/local/FileManagerLocal.kt @@ -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 = 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/data/local/IFileManager.kt b/app/src/main/java/be/re/writand/data/local/IFileManager.kt new file mode 100644 index 0000000..a8376c7 --- /dev/null +++ b/app/src/main/java/be/re/writand/data/local/IFileManager.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/test/java/be/re/writand/ExampleUnitTest.kt b/app/src/test/java/be/re/writand/ExampleUnitTest.kt deleted file mode 100644 index efad5f8..0000000 --- a/app/src/test/java/be/re/writand/ExampleUnitTest.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/test/java/be/re/writand/FileManagerLocalTest.kt b/app/src/test/java/be/re/writand/FileManagerLocalTest.kt new file mode 100644 index 0000000..953fa19 --- /dev/null +++ b/app/src/test/java/be/re/writand/FileManagerLocalTest.kt @@ -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)) + } +} \ No newline at end of file