summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-07-15 18:35:04 +0200
committerJoel Klinghed <the_jk@spawned.biz>2024-07-15 18:35:04 +0200
commit23d3e4de4a4442af8e5312bda9a36b4c587a3a8c (patch)
tree5a79861f95ad2d667baf5bfc53097b6a99ad80fe
parente67a5599fda60e7ceaaf188eb8073325c6344473 (diff)
Fix some problems with Link
And add more unittests for all of Directory, File and Link.
-rw-r--r--app/src/main/java/org/the_jk/cleversync/io/Link.kt4
-rw-r--r--app/src/main/java/org/the_jk/cleversync/io/ModifiableDirectory.kt2
-rw-r--r--app/src/main/java/org/the_jk/cleversync/io/ModifiableLink.kt10
-rw-r--r--app/src/main/java/org/the_jk/cleversync/io/impl/PathDirectory.kt21
-rw-r--r--app/src/main/java/org/the_jk/cleversync/io/impl/PathFile.kt3
-rw-r--r--app/src/main/java/org/the_jk/cleversync/io/impl/PathLink.kt25
-rw-r--r--app/src/test/java/org/the_jk/cleversync/io/LocalTreeTest.kt206
7 files changed, 250 insertions, 21 deletions
diff --git a/app/src/main/java/org/the_jk/cleversync/io/Link.kt b/app/src/main/java/org/the_jk/cleversync/io/Link.kt
index 3ecf5e6..c05f29e 100644
--- a/app/src/main/java/org/the_jk/cleversync/io/Link.kt
+++ b/app/src/main/java/org/the_jk/cleversync/io/Link.kt
@@ -7,7 +7,7 @@ interface Link {
sealed class LinkTarget
- class DirectoryTarget(val directory: Directory): LinkTarget()
- class FileTarget(val file: File): LinkTarget()
+ data class DirectoryTarget(val directory: Directory): LinkTarget()
+ data class FileTarget(val file: File): LinkTarget()
data object NoTarget: LinkTarget()
}
diff --git a/app/src/main/java/org/the_jk/cleversync/io/ModifiableDirectory.kt b/app/src/main/java/org/the_jk/cleversync/io/ModifiableDirectory.kt
index 32d288a..8bddc2c 100644
--- a/app/src/main/java/org/the_jk/cleversync/io/ModifiableDirectory.kt
+++ b/app/src/main/java/org/the_jk/cleversync/io/ModifiableDirectory.kt
@@ -12,6 +12,8 @@ interface ModifiableDirectory : Directory {
fun createDirectory(name: String): ModifiableDirectory
fun createFile(name: String): ModifiableFile
+ fun createLink(name: String, target: Directory): ModifiableLink
+ fun createLink(name: String, target: File): ModifiableLink
fun createLink(name: String, target: String): ModifiableLink
fun removeDirectory(name: String): Boolean
diff --git a/app/src/main/java/org/the_jk/cleversync/io/ModifiableLink.kt b/app/src/main/java/org/the_jk/cleversync/io/ModifiableLink.kt
index a20bb6a..7dd565b 100644
--- a/app/src/main/java/org/the_jk/cleversync/io/ModifiableLink.kt
+++ b/app/src/main/java/org/the_jk/cleversync/io/ModifiableLink.kt
@@ -3,13 +3,13 @@ package org.the_jk.cleversync.io
interface ModifiableLink : Link {
fun modifiableResolve(): ModifiableLinkTarget
- fun target(directory: Directory) = target(directory.name)
- fun target(file: File) = target(file.name)
+ fun target(directory: Directory)
+ fun target(file: File)
fun target(name: String)
sealed class ModifiableLinkTarget
- class ModifiableDirectoryTarget(val directory: ModifiableDirectory): ModifiableLinkTarget()
- class ModifiableFileTarget(val file: ModifiableFile): ModifiableLinkTarget()
- data object ModifiableNoTarget: ModifiableLinkTarget()
+ data class ModifiableDirectoryTarget(val directory: ModifiableDirectory): ModifiableLinkTarget()
+ data class ModifiableFileTarget(val file: ModifiableFile): ModifiableLinkTarget()
+ data object NoTarget: ModifiableLinkTarget()
}
diff --git a/app/src/main/java/org/the_jk/cleversync/io/impl/PathDirectory.kt b/app/src/main/java/org/the_jk/cleversync/io/impl/PathDirectory.kt
index 730e048..55bed6a 100644
--- a/app/src/main/java/org/the_jk/cleversync/io/impl/PathDirectory.kt
+++ b/app/src/main/java/org/the_jk/cleversync/io/impl/PathDirectory.kt
@@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import org.the_jk.cleversync.io.Directory
+import org.the_jk.cleversync.io.File
import org.the_jk.cleversync.io.ModifiableDirectory
import org.the_jk.cleversync.io.ModifiableFile
import org.the_jk.cleversync.io.ModifiableLink
@@ -25,7 +26,7 @@ import kotlin.io.path.readSymbolicLink
@OptIn(ExperimentalPathApi::class)
internal open class PathDirectory(
- private val path: Path,
+ internal val path: Path,
private val pathWatcher: PathWatcher,
) : ModifiableDirectory {
private val watcher: DirectoryWatcher by lazy {
@@ -51,7 +52,7 @@ internal open class PathDirectory(
if (path.isDirectory(LinkOption.NOFOLLOW_LINKS)) return PathDirectory(path, pathWatcher)
if (path.isSymbolicLink()) {
val target = path.readSymbolicLink()
- if (target.isDirectory()) return PathDirectory(target, pathWatcher)
+ if (target.isDirectory()) return PathDirectory(target.toRealPath(), pathWatcher)
}
return null
}
@@ -61,7 +62,7 @@ internal open class PathDirectory(
if (path.isRegularFile(LinkOption.NOFOLLOW_LINKS)) return PathFile(path)
if (path.isSymbolicLink()) {
val target = path.readSymbolicLink()
- if (target.isRegularFile()) return PathFile(target)
+ if (target.isRegularFile()) return PathFile(target.toRealPath())
}
return null
}
@@ -84,9 +85,20 @@ internal open class PathDirectory(
return PathFile(path)
}
+ override fun createLink(name: String, target: Directory): ModifiableLink {
+ val path = path.resolve(name)
+ return PathLink(path.createSymbolicLinkPointingTo((target as PathDirectory).path), pathWatcher)
+ }
+
+ override fun createLink(name: String, target: File): ModifiableLink {
+ val path = path.resolve(name)
+ return PathLink(path.createSymbolicLinkPointingTo((target as PathFile).path), pathWatcher)
+ }
+
override fun createLink(name: String, target: String): ModifiableLink {
+ val targetPath = path.resolve(target)
val path = path.resolve(name)
- return PathLink(path.createSymbolicLinkPointingTo(path.resolve(target)), pathWatcher)
+ return PathLink(path.createSymbolicLinkPointingTo(targetPath), pathWatcher)
}
override fun removeDirectory(name: String): Boolean {
@@ -123,6 +135,7 @@ internal open class PathDirectory(
override fun equals(other: Any?) = other is PathDirectory && other.path == path
override fun hashCode() = path.hashCode()
+ override fun toString() = path.toString()
private inner class DirectoryWatcher : PathWatcher.Delegate {
val content: LiveData<ModifiableDirectory.Content>
diff --git a/app/src/main/java/org/the_jk/cleversync/io/impl/PathFile.kt b/app/src/main/java/org/the_jk/cleversync/io/impl/PathFile.kt
index 2ccc37c..9a8d160 100644
--- a/app/src/main/java/org/the_jk/cleversync/io/impl/PathFile.kt
+++ b/app/src/main/java/org/the_jk/cleversync/io/impl/PathFile.kt
@@ -16,7 +16,7 @@ import kotlin.io.path.inputStream
import kotlin.io.path.name
import kotlin.io.path.outputStream
-internal class PathFile(private val path: Path) : ModifiableFile {
+internal class PathFile(internal val path: Path) : ModifiableFile {
override fun write(): OutputStream {
// If file doesn't exist, write to it directly.
if (!path.exists(LinkOption.NOFOLLOW_LINKS))
@@ -62,4 +62,5 @@ internal class PathFile(private val path: Path) : ModifiableFile {
override fun equals(other: Any?) = other is PathFile && other.path == path
override fun hashCode() = path.hashCode()
+ override fun toString() = path.toString()
}
diff --git a/app/src/main/java/org/the_jk/cleversync/io/impl/PathLink.kt b/app/src/main/java/org/the_jk/cleversync/io/impl/PathLink.kt
index 5d11228..9ae8b51 100644
--- a/app/src/main/java/org/the_jk/cleversync/io/impl/PathLink.kt
+++ b/app/src/main/java/org/the_jk/cleversync/io/impl/PathLink.kt
@@ -1,5 +1,7 @@
package org.the_jk.cleversync.io.impl
+import org.the_jk.cleversync.io.Directory
+import org.the_jk.cleversync.io.File
import org.the_jk.cleversync.io.Link
import org.the_jk.cleversync.io.ModifiableLink
import java.nio.file.Path
@@ -17,17 +19,27 @@ internal class PathLink(
override fun modifiableResolve(): ModifiableLink.ModifiableLinkTarget {
val target = path.readSymbolicLink()
return if (target.isDirectory()) {
- ModifiableLink.ModifiableDirectoryTarget(PathDirectory(target, pathWatcher))
+ ModifiableLink.ModifiableDirectoryTarget(PathDirectory(target.toRealPath(), pathWatcher))
} else if (target.isRegularFile()) {
- ModifiableLink.ModifiableFileTarget(PathFile(target))
+ ModifiableLink.ModifiableFileTarget(PathFile(target.toRealPath()))
} else {
- ModifiableLink.ModifiableNoTarget
+ ModifiableLink.NoTarget
}
}
+ override fun target(directory: Directory) {
+ path.deleteIfExists()
+ path.createSymbolicLinkPointingTo((directory as PathDirectory).path)
+ }
+
+ override fun target(file: File) {
+ path.deleteIfExists()
+ path.createSymbolicLinkPointingTo((file as PathFile).path)
+ }
+
override fun target(name: String) {
path.deleteIfExists()
- path.createSymbolicLinkPointingTo(path.resolve(name))
+ path.createSymbolicLinkPointingTo(path.parent.resolve(name))
}
override val name: String
@@ -35,13 +47,14 @@ internal class PathLink(
override fun equals(other: Any?) = other is PathLink && other.path == path
override fun hashCode() = path.hashCode()
+ override fun toString() = path.toString()
override fun resolve(): Link.LinkTarget {
val target = path.readSymbolicLink()
return if (target.isDirectory()) {
- Link.DirectoryTarget(PathDirectory(target, pathWatcher))
+ Link.DirectoryTarget(PathDirectory(target.toRealPath(), pathWatcher))
} else if (target.isRegularFile()) {
- Link.FileTarget(PathFile(target))
+ Link.FileTarget(PathFile(target.toRealPath()))
} else {
Link.NoTarget
}
diff --git a/app/src/test/java/org/the_jk/cleversync/io/LocalTreeTest.kt b/app/src/test/java/org/the_jk/cleversync/io/LocalTreeTest.kt
index ea2fd91..7b83c98 100644
--- a/app/src/test/java/org/the_jk/cleversync/io/LocalTreeTest.kt
+++ b/app/src/test/java/org/the_jk/cleversync/io/LocalTreeTest.kt
@@ -1,6 +1,7 @@
package org.the_jk.cleversync.io
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -90,13 +91,15 @@ class LocalTreeTest {
os.write(byteArrayOf(1, 2, 3, 4))
}
foo.write().use { os ->
- os.write(byteArrayOf(127))
+ os.write(127)
+ os.write(byteArrayOf(1))
+ os.write(byteArrayOf(2), 0, 0)
assertThat(foo.size).isEqualTo(4.toULong())
}
- assertThat(foo.size).isEqualTo(1.toULong())
+ assertThat(foo.size).isEqualTo(2.toULong())
assertThat(tree.list().files).hasSize(1)
foo.read().use {
- assertThat(it.readBytes()).isEqualTo(byteArrayOf(127))
+ assertThat(it.readBytes()).isEqualTo(byteArrayOf(127, 1))
}
}
@@ -120,4 +123,201 @@ class LocalTreeTest {
ShadowLooper.idleMainLooper()
}
}
+
+ @Test
+ fun createLink() {
+ val dir = tree.createDirectory("dir")
+ val file = tree.createFile("file")
+ val link = tree.createLink("link", dir.name)
+ var target = link.resolve()
+ when (target) {
+ is Link.DirectoryTarget -> assertThat(target.directory).isEqualTo(
+ dir
+ )
+ is Link.FileTarget -> Assert.fail()
+ is Link.NoTarget -> Assert.fail()
+ }
+ assertThat(tree.openDir("link")).isEqualTo(dir)
+
+ link.target(file)
+ target = link.resolve()
+ when (target) {
+ is Link.DirectoryTarget -> Assert.fail()
+ is Link.FileTarget -> Assert.fail()
+ is Link.NoTarget -> Unit
+ }
+ file.write().use { it.write(1) }
+ target = link.resolve()
+ when (target) {
+ is Link.DirectoryTarget -> Assert.fail()
+ is Link.FileTarget -> assertThat(target.file).isEqualTo(file)
+ is Link.NoTarget -> Assert.fail()
+ }
+
+ assertThat(tree.openFile("link")).isEqualTo(file)
+ }
+
+ @Test
+ fun createLinkSubdir() {
+ val foo = tree.createDirectory("foo")
+ val bar = foo.createDirectory("bar")
+ val link1 = tree.createLink("link1", "foo/bar")
+ val link2 = tree.createLink("link2", bar)
+ assertThat(link1.resolve()).isEqualTo(link2.resolve())
+ assertThat((link1.resolve() as Link.DirectoryTarget).directory).isEqualTo(bar)
+ val link3 = foo.createLink("link3", "../link1")
+ assertThat((link3.resolve() as Link.DirectoryTarget).directory).isEqualTo(bar)
+ }
+
+ @Test
+ fun createLiveLink() {
+ val content = tree.liveList()
+ var link: Link? = null
+ content.observeForever {
+ if (it.links.size == 1) link = it.links[0]
+ }
+ val dir = tree.createDirectory("dir")
+ tree.createLink("link", "dir")
+ while (link == null) {
+ ShadowLooper.idleMainLooper()
+ }
+ assertThat((link?.resolve() as Link.DirectoryTarget).directory).isEqualTo(dir)
+ }
+
+ @Test
+ fun sameDir() {
+ val dir1 = tree.createDirectory("dir")
+ val dir2 = tree.openDir("dir")
+ assertThat(dir1).isEqualTo(dir2)
+ assertThat(dir1.hashCode()).isEqualTo(dir2.hashCode())
+ assertThat(dir1.toString()).isEqualTo(dir2.toString())
+ }
+
+ @Test
+ fun sameFile() {
+ val file1 = tree.createFile("file")
+ file1.write().use { it.write(127) }
+ val file2 = tree.openFile("file")
+ assertThat(file1).isEqualTo(file2)
+ assertThat(file1.hashCode()).isEqualTo(file2.hashCode())
+ assertThat(file1.toString()).isEqualTo(file2.toString())
+ }
+
+ @Test
+ fun sameLink() {
+ val link1 = tree.createLink("link", "foo")
+ val link2 = tree.openLink("link")
+ assertThat(link1).isEqualTo(link2)
+ assertThat(link1.hashCode()).isEqualTo(link2.hashCode())
+ assertThat(link1.toString()).isEqualTo(link2.toString())
+ }
+
+ @Test
+ fun removeDirWithContent() {
+ val foo = tree.createDirectory("foo")
+ foo.createDirectory("dir")
+ foo.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) }
+ foo.createLink("link", "file")
+ assertThat(tree.list().directories).hasSize(1)
+ assertThat(tree.removeDirectory("foo")).isTrue()
+ assertThat(tree.list().directories).isEmpty()
+ }
+
+ @Test
+ fun removeWrongType() {
+ tree.createDirectory("dir")
+ assertThat(tree.removeFile("dir")).isFalse()
+ assertThat(tree.removeLink("dir")).isFalse()
+ tree.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) }
+ assertThat(tree.removeDirectory("file")).isFalse()
+ assertThat(tree.removeLink("file")).isFalse()
+ tree.createLink("link", "doesn't exist")
+ assertThat(tree.removeDirectory("link")).isFalse()
+ assertThat(tree.removeFile("link")).isFalse()
+ val content = tree.list()
+ assertThat(content.directories).hasSize(1)
+ assertThat(content.files).hasSize(1)
+ assertThat(content.links).hasSize(1)
+ }
+
+ @Test
+ fun removeFile() {
+ tree.createFile("file").write().use { it.write(byteArrayOf(1, 2, 3, 4)) }
+ assertThat(tree.list().files).hasSize(1)
+ tree.removeFile("file")
+ assertThat(tree.list().files).isEmpty()
+ }
+
+ @Test
+ fun removeLink() {
+ val dir = tree.createDirectory("dir")
+ val file = tree.createFile("file")
+ file.write().use { it.write(127) }
+ tree.createLink("link1", dir)
+ tree.createLink("link2", file)
+ assertThat(tree.list().links).hasSize(2)
+ tree.removeLink("link1")
+ tree.removeLink("link2")
+ assertThat(tree.list().links).isEmpty()
+ }
+
+ @Test
+ fun changeLink() {
+ val dir = tree.createDirectory("dir")
+ val file = tree.createFile("file")
+ file.write().use { it.write(127) }
+ val link = tree.createLink("link", "doesn't exist")
+ assertThat(link.resolve() is Link.NoTarget).isTrue()
+ link.target(file)
+ assertThat((link.resolve() as Link.FileTarget).file).isEqualTo(file)
+ link.target(dir)
+ assertThat((link.resolve() as Link.DirectoryTarget).directory).isEqualTo(dir)
+ link.target("bad")
+ assertThat(link.resolve() is Link.NoTarget).isTrue()
+ }
+
+ @Test
+ fun changeModifiableLink() {
+ val dir = tree.createDirectory("dir")
+ val file = tree.createFile("file")
+ file.write().use { it.write(127) }
+ val link = tree.createLink("link", "doesn't exist")
+ assertThat(link.modifiableResolve() is ModifiableLink.NoTarget).isTrue()
+ link.target(file)
+ assertThat((link.modifiableResolve() as ModifiableLink.ModifiableFileTarget).file).isEqualTo(file)
+ link.target(dir)
+ assertThat((link.modifiableResolve() as ModifiableLink.ModifiableDirectoryTarget).directory).isEqualTo(dir)
+ link.target("bad")
+ assertThat(link.modifiableResolve() is ModifiableLink.NoTarget).isTrue()
+ }
+
+ @Test
+ fun recursiveLink() {
+ val link = tree.createLink("link", "link")
+ assertThat(link.resolve() is Link.NoTarget).isTrue()
+ }
+
+ @Test
+ fun names() {
+ assertThat(tree.createDirectory("dir").name).isEqualTo("dir")
+ assertThat(tree.createFile("file").name).isEqualTo("file")
+ assertThat(tree.createLink("link", "file").name).isEqualTo("link")
+ }
+
+ @Test
+ fun openNonExistent() {
+ assertThat(tree.openDir("dir")).isNull()
+ assertThat(tree.openFile("file")).isNull()
+ assertThat(tree.openLink("link")).isNull()
+ }
+
+ @Test
+ fun lastModified() {
+ val file = tree.createFile("foo")
+ file.write().use { it.write(1) }
+ val old = file.lastModified
+ file.write().use { it.write(2); it.flush() }
+ val new = file.lastModified
+ assertThat(old.isBefore(new) || old == new).isTrue()
+ }
}