summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-09-25 22:16:26 +0200
committerJoel Klinghed <the_jk@spawned.biz>2024-09-25 22:16:26 +0200
commitf03c738a9e39053ad185084bf83bf6e526011b71 (patch)
tree86baa67bb76e7e7f2f12a41afd8ddc8ea3506164
parent2d8949a0d9333bfda2385e3eab124581704286bf (diff)
samba & sftp: Share path utils methods
Add tests for them, and add basename, currently unused, for completeness
-rw-r--r--libs/samba/build.gradle.kts2
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt22
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt17
-rw-r--r--libs/sftp/build.gradle.kts2
-rw-r--r--libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpConnection.kt53
-rw-r--r--libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpDirectory.kt23
-rw-r--r--libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpLink.kt5
-rw-r--r--libs/utils/src/main/java/org/the_jk/cleversync/PathUtils.kt49
-rw-r--r--libs/utils/src/test/java/org/the_jk/cleversync/PathUtilsTest.kt58
9 files changed, 151 insertions, 80 deletions
diff --git a/libs/samba/build.gradle.kts b/libs/samba/build.gradle.kts
index a86ad39..4926cc4 100644
--- a/libs/samba/build.gradle.kts
+++ b/libs/samba/build.gradle.kts
@@ -39,7 +39,7 @@ android {
dependencies {
implementation(project(":libs:io"))
- testImplementation(project(":libs:utils"))
+ implementation(project(":libs:utils"))
testImplementation(project(":libs:test-utils"))
}
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt
index 7378ab5..00a1ba7 100644
--- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt
@@ -1,5 +1,7 @@
package org.the_jk.cleversync.io.samba
+import org.the_jk.cleversync.PathUtils
+
internal class SambaConnection(uri: String, credentials: SambaCredentials) {
val description = uri
private val context = NativeSamba.newContext()
@@ -23,30 +25,22 @@ internal class SambaConnection(uri: String, credentials: SambaCredentials) {
}
fun openDir(path: String): NativeSamba.Dir? =
- if (connected) context.openDir(join(url!!.path(), path)) else null
+ if (connected) context.openDir(PathUtils.join(url!!.path(), path)) else null
fun entry(path: String): NativeSamba.DirEntry? =
- if (connected) context.entry(join(url!!.path(), path)) else null
+ if (connected) context.entry(PathUtils.join(url!!.path(), path)) else null
fun makeDir(path: String): Boolean =
- connected && context.makeDir(join(url!!.path(), path))
+ connected && context.makeDir(PathUtils.join(url!!.path(), path))
fun removeDir(path: String): Boolean =
- connected && context.removeDir(join(url!!.path(), path))
+ connected && context.removeDir(PathUtils.join(url!!.path(), path))
fun unlink(path: String): Boolean =
- connected && context.unlink(join(url!!.path(), path))
+ connected && context.unlink(PathUtils.join(url!!.path(), path))
fun openFile(path: String, read: NativeSamba.OpenMode): NativeSamba.File? =
- if (connected) context.openFile(join(url!!.path(), path), read) else null
+ if (connected) context.openFile(PathUtils.join(url!!.path(), path), read) else null
override fun toString() = description
-
- companion object {
- fun join(a: String, b: String): String {
- if (a.isEmpty() || b.startsWith("/")) return b
- if (b.isEmpty()) return a
- return if (a.endsWith("/")) a + b else "${a}/${b}"
- }
- }
}
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt
index 67228a4..4865d7f 100644
--- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt
@@ -5,6 +5,7 @@ import android.os.Looper
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
+import org.the_jk.cleversync.PathUtils
import org.the_jk.cleversync.io.Directory
import org.the_jk.cleversync.io.File
import org.the_jk.cleversync.io.ModifiableDirectory
@@ -55,14 +56,14 @@ internal open class SambaDirectory(
}
override fun modifiableOpenDir(name: String): ModifiableDirectory? {
- val newPath = SambaConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
val entry = conn.entry(newPath) ?: return null
if (entry.type != NativeSamba.DirEntryType.DIR) return null
return SambaDirectory(conn, newPath, name)
}
override fun modifiableOpenFile(name: String): ModifiableFile? {
- val newPath = SambaConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
val entry = conn.entry(newPath) ?: return null
if (entry.type != NativeSamba.DirEntryType.FILE) return null
return SambaFile(conn, newPath, name, entry.size, entry.lastModified)
@@ -79,7 +80,7 @@ internal open class SambaDirectory(
val dir = conn.openDir(path)
if (dir != null) {
dir.list().forEach { entry ->
- val entryPath = SambaConnection.join(path, entry.name)
+ val entryPath = PathUtils.join(path, entry.name)
when (entry.type) {
NativeSamba.DirEntryType.DIR -> {
directories.add(SambaDirectory(conn, entryPath, entry.name))
@@ -97,13 +98,13 @@ internal open class SambaDirectory(
override fun modifiableLiveList() = modifiableLiveContent
override fun createDirectory(name: String): ModifiableDirectory {
- val newPath = SambaConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
if (!conn.makeDir(newPath)) throw IOException(conn.error)
return SambaDirectory(conn, newPath, name)
}
override fun createFile(name: String): ModifiableFile {
- val newPath = SambaConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
return SambaFile(conn, newPath, name, 0UL, Instant.EPOCH, Instant.EPOCH)
}
@@ -120,7 +121,7 @@ internal open class SambaDirectory(
}
override fun removeDirectory(name: String): Boolean {
- val removePath = SambaConnection.join(path, name)
+ val removePath = PathUtils.join(path, name)
val entry = conn.entry(removePath) ?: return false
if (entry.type != NativeSamba.DirEntryType.DIR) return false
return removeRecursive(removePath)
@@ -130,7 +131,7 @@ internal open class SambaDirectory(
val dir = conn.openDir(removePath) ?: return false
try {
dir.list().forEach { entry ->
- val entryPath = SambaConnection.join(removePath, entry.name)
+ val entryPath = PathUtils.join(removePath, entry.name)
if (!when (entry.type) {
NativeSamba.DirEntryType.FILE
-> conn.unlink(entryPath)
@@ -147,7 +148,7 @@ internal open class SambaDirectory(
}
override fun removeFile(name: String): Boolean {
- val removePath = SambaConnection.join(path, name)
+ val removePath = PathUtils.join(path, name)
val entry = conn.entry(removePath) ?: return false
if (entry.type != NativeSamba.DirEntryType.FILE) return false
return conn.unlink(removePath)
diff --git a/libs/sftp/build.gradle.kts b/libs/sftp/build.gradle.kts
index 4ccac9c..8fd26c2 100644
--- a/libs/sftp/build.gradle.kts
+++ b/libs/sftp/build.gradle.kts
@@ -62,7 +62,7 @@ android {
dependencies {
implementation(project(":libs:io"))
- testImplementation(project(":libs:utils"))
+ implementation(project(":libs:utils"))
testImplementation(project(":libs:test-utils"))
}
diff --git a/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpConnection.kt b/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpConnection.kt
index 706116b..9f05138 100644
--- a/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpConnection.kt
+++ b/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpConnection.kt
@@ -1,6 +1,7 @@
package org.the_jk.cleversync.io.sftp
import android.net.Uri
+import org.the_jk.cleversync.PathUtils
internal class SftpConnection(uri: Uri, credentials: SftpCredentials) {
val description = uri.toString()
@@ -29,22 +30,22 @@ internal class SftpConnection(uri: Uri, credentials: SftpCredentials) {
}
fun openDir(path: String): NativeSftp.Dir? =
- sftpSession?.openDir(join(baseDir, path))
+ sftpSession?.openDir(PathUtils.join(baseDir, path))
fun entry(path: String, followLink: Boolean = true): NativeSftp.DirEntry? =
- sftpSession?.stat(join(baseDir, path), followLink)
+ sftpSession?.stat(PathUtils.join(baseDir, path), followLink)
fun makeDir(path: String): Boolean =
- sftpSession?.makeDir(join(baseDir, path), 511 /* 0777 */) ?: false
+ sftpSession?.makeDir(PathUtils.join(baseDir, path), 511 /* 0777 */) ?: false
fun removeDir(path: String): Boolean =
- sftpSession?.removeDir(join(baseDir, path)) ?: false
+ sftpSession?.removeDir(PathUtils.join(baseDir, path)) ?: false
fun unlink(path: String): Boolean =
- sftpSession?.unlink(join(baseDir, path)) ?: false
+ sftpSession?.unlink(PathUtils.join(baseDir, path)) ?: false
fun readLink(path: String): String? {
- val target = sftpSession?.readlink(join(baseDir, path))
+ val target = sftpSession?.readlink(PathUtils.join(baseDir, path))
if (target?.startsWith(baseDir) == true) {
return target.substring(baseDir.length + 1)
}
@@ -55,13 +56,13 @@ internal class SftpConnection(uri: Uri, credentials: SftpCredentials) {
val relativeTarget = if (rawTarget) {
target
} else {
- join(baseDir, target)
+ PathUtils.join(baseDir, target)
}
- return sftpSession?.symlink(relativeTarget, join(baseDir, path)) ?: false
+ return sftpSession?.symlink(relativeTarget, PathUtils.join(baseDir, path)) ?: false
}
fun openFile(path: String, mode: NativeSftp.OpenMode): NativeSftp.File? =
- sftpSession?.openFile(join(baseDir, path), mode)
+ sftpSession?.openFile(PathUtils.join(baseDir, path), mode)
override fun toString() = description
@@ -94,39 +95,5 @@ internal class SftpConnection(uri: Uri, credentials: SftpCredentials) {
companion object {
private const val DEFAULT_PORT = 22
-
- fun join(a: String, b: String): String {
- if (a.isEmpty() || b.startsWith("/")) return b
- if (b.isEmpty()) return a
- return if (a.endsWith("/")) a + b else "${a}/${b}"
- }
-
- fun dirname(path: String): String {
- var start = path.lastIndex
- while (start > -1 && path[start] == '/') start--;
- if (start > -1) {
- val index = path.lastIndexOf('/', startIndex = start)
- if (index > -1) return path.substring(0, index)
- }
- return ""
- }
-
- fun resolve(path: String): String {
- val parts = path.split('/').filterIndexed { index, part ->
- index == 0 || (part.isNotEmpty() && part != ".")
- }.toMutableList()
- var i = 1
- while (i < parts.size) {
- if (parts[i] == "..") {
- parts.removeAt(i)
- if (parts[i].isNotEmpty()) {
- parts.removeAt(i - 1)
- }
- } else {
- i++
- }
- }
- return parts.joinToString("/")
- }
}
}
diff --git a/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpDirectory.kt b/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpDirectory.kt
index d547471..90a3127 100644
--- a/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpDirectory.kt
+++ b/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpDirectory.kt
@@ -5,6 +5,7 @@ import android.os.Looper
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
+import org.the_jk.cleversync.PathUtils
import org.the_jk.cleversync.io.Directory
import org.the_jk.cleversync.io.File
import org.the_jk.cleversync.io.ModifiableDirectory
@@ -55,21 +56,21 @@ internal open class SftpDirectory(
}
override fun modifiableOpenDir(name: String): ModifiableDirectory? {
- val newPath = SftpConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
val entry = conn.entry(newPath) ?: return null
if (entry.type != NativeSftp.DirEntryType.DIR) return null
return SftpDirectory(conn, newPath, name)
}
override fun modifiableOpenFile(name: String): ModifiableFile? {
- val newPath = SftpConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
val entry = conn.entry(newPath) ?: return null
if (entry.type != NativeSftp.DirEntryType.FILE) return null
return SftpFile(conn, newPath, name, entry.size, entry.lastModified)
}
override fun modifiableOpenLink(name: String): ModifiableLink? {
- val newPath = SftpConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
val entry = conn.entry(newPath, followLink = false) ?: return null
if (entry.type != NativeSftp.DirEntryType.LINK) return null
return SftpLink(conn, newPath, name)
@@ -82,7 +83,7 @@ internal open class SftpDirectory(
val dir = conn.openDir(path)
if (dir != null) {
dir.list().forEach { entry ->
- val entryPath = SftpConnection.join(path, entry.name)
+ val entryPath = PathUtils.join(path, entry.name)
when (entry.type) {
NativeSftp.DirEntryType.DIR -> {
directories.add(SftpDirectory(conn, entryPath, entry.name))
@@ -103,13 +104,13 @@ internal open class SftpDirectory(
override fun modifiableLiveList() = modifiableLiveContent
override fun createDirectory(name: String): ModifiableDirectory {
- val newPath = SftpConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
if (!conn.makeDir(newPath)) throw IOException(conn.error)
return SftpDirectory(conn, newPath, name)
}
override fun createFile(name: String): ModifiableFile {
- val newPath = SftpConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
return SftpFile(conn, newPath, name, 0UL, Instant.EPOCH, Instant.EPOCH)
}
@@ -126,13 +127,13 @@ internal open class SftpDirectory(
}
private fun createLink(name: String, target: String, rawTarget: Boolean): ModifiableLink {
- val newPath = SftpConnection.join(path, name)
+ val newPath = PathUtils.join(path, name)
if (!conn.symlink(target, rawTarget, newPath)) throw IOException(conn.error)
return SftpLink(conn, newPath, name)
}
override fun removeDirectory(name: String): Boolean {
- val removePath = SftpConnection.join(path, name)
+ val removePath = PathUtils.join(path, name)
val entry = conn.entry(removePath) ?: return false
if (entry.type != NativeSftp.DirEntryType.DIR) return false
return removeRecursive(removePath)
@@ -142,7 +143,7 @@ internal open class SftpDirectory(
val dir = conn.openDir(removePath) ?: return false
try {
dir.list().forEach { entry ->
- val entryPath = SftpConnection.join(removePath, entry.name)
+ val entryPath = PathUtils.join(removePath, entry.name)
if (!when (entry.type) {
NativeSftp.DirEntryType.FILE,
NativeSftp.DirEntryType.LINK,
@@ -160,14 +161,14 @@ internal open class SftpDirectory(
}
override fun removeFile(name: String): Boolean {
- val removePath = SftpConnection.join(path, name)
+ val removePath = PathUtils.join(path, name)
val entry = conn.entry(removePath) ?: return false
if (entry.type != NativeSftp.DirEntryType.FILE) return false
return conn.unlink(removePath)
}
override fun removeLink(name: String): Boolean {
- val removePath = SftpConnection.join(path, name)
+ val removePath = PathUtils.join(path, name)
val entry = conn.entry(removePath, followLink = false) ?: return false
if (entry.type != NativeSftp.DirEntryType.LINK) return false
return conn.unlink(removePath)
diff --git a/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpLink.kt b/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpLink.kt
index c2259ac..a922e26 100644
--- a/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpLink.kt
+++ b/libs/sftp/src/main/java/org/the_jk/cleversync/io/sftp/SftpLink.kt
@@ -1,5 +1,6 @@
package org.the_jk.cleversync.io.sftp
+import org.the_jk.cleversync.PathUtils
import org.the_jk.cleversync.io.Directory
import org.the_jk.cleversync.io.ModifiableLink
import org.the_jk.cleversync.io.File
@@ -65,8 +66,8 @@ internal class SftpLink(
var entry: NativeSftp.DirEntry? = null
while (true) {
val target = conn.readLink(linkPath) ?: break
- linkPath = SftpConnection.resolve(
- SftpConnection.join(SftpConnection.dirname(linkPath), target),
+ linkPath = PathUtils.resolve(
+ PathUtils.join(PathUtils.dirname(linkPath), target),
)
if (!paths.add(linkPath)) break
entry = conn.entry(linkPath, followLink = false) ?: break
diff --git a/libs/utils/src/main/java/org/the_jk/cleversync/PathUtils.kt b/libs/utils/src/main/java/org/the_jk/cleversync/PathUtils.kt
new file mode 100644
index 0000000..4202b24
--- /dev/null
+++ b/libs/utils/src/main/java/org/the_jk/cleversync/PathUtils.kt
@@ -0,0 +1,49 @@
+package org.the_jk.cleversync
+
+object PathUtils {
+ fun dirname(path: String): String {
+ if (path.isEmpty()) return "."
+ var start = path.lastIndex
+ while (start > -1 && path[start] == '/') start--
+ if (start == -1) return "/"
+ val index = path.lastIndexOf('/', startIndex = start)
+ if (index == 0) return "/"
+ if (index > -1) return path.substring(0, index)
+ return "."
+ }
+
+ fun basename(path: String): String {
+ if (path.isEmpty()) return ""
+ var start = path.lastIndex
+ while (start > -1 && path[start] == '/') start--
+ if (start == -1) return "/"
+ val index = path.lastIndexOf('/', startIndex = start)
+ return path.substring(index + 1, start + 1)
+ }
+
+ fun join(path1: String, path2: String): String {
+ if (path1.isEmpty() || path2.startsWith("/")) return path2
+ if (path2.isEmpty()) return path1
+ return if (path1.endsWith("/")) path1 + path2 else "${path1}/${path2}"
+ }
+
+ fun resolve(path: String): String {
+ if (path.isEmpty()) return ""
+ val prefixed = path.startsWith("/")
+ val parts = path.split('/').filter { part ->
+ part.isNotEmpty() && part != "."
+ }.toMutableList()
+ var i = 0
+ while (i < parts.size) {
+ if (parts[i] == "..") {
+ parts.removeAt(i)
+ if (i > 0) {
+ parts.removeAt(i - 1)
+ }
+ } else {
+ i++
+ }
+ }
+ return parts.joinToString("/", prefix = if (prefixed) "/" else "")
+ }
+}
diff --git a/libs/utils/src/test/java/org/the_jk/cleversync/PathUtilsTest.kt b/libs/utils/src/test/java/org/the_jk/cleversync/PathUtilsTest.kt
new file mode 100644
index 0000000..eb1d63f
--- /dev/null
+++ b/libs/utils/src/test/java/org/the_jk/cleversync/PathUtilsTest.kt
@@ -0,0 +1,58 @@
+package org.the_jk.cleversync
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class PathUtilsTest {
+ @Test
+ fun dirname() {
+ assertThat(PathUtils.dirname("")).isEqualTo(".")
+ assertThat(PathUtils.dirname("foo")).isEqualTo(".")
+ assertThat(PathUtils.dirname("foo/")).isEqualTo(".")
+ assertThat(PathUtils.dirname("foo/bar")).isEqualTo("foo")
+ assertThat(PathUtils.dirname("/")).isEqualTo("/")
+ assertThat(PathUtils.dirname("//")).isEqualTo("/")
+ assertThat(PathUtils.dirname("/foo")).isEqualTo("/")
+ assertThat(PathUtils.dirname("/foo/")).isEqualTo("/")
+ }
+
+ @Test
+ fun basename() {
+ assertThat(PathUtils.basename("")).isEmpty()
+ assertThat(PathUtils.basename("foo")).isEqualTo("foo")
+ assertThat(PathUtils.basename("foo/")).isEqualTo("foo")
+ assertThat(PathUtils.basename("foo/bar")).isEqualTo("bar")
+ assertThat(PathUtils.basename("/")).isEqualTo("/")
+ assertThat(PathUtils.basename("//")).isEqualTo("/")
+ assertThat(PathUtils.basename("/foo")).isEqualTo("foo")
+ assertThat(PathUtils.basename("/foo/")).isEqualTo("foo")
+ }
+
+ @Test
+ fun join() {
+ assertThat(PathUtils.join("", "")).isEmpty()
+ assertThat(PathUtils.join("/", "")).isEqualTo("/")
+ assertThat(PathUtils.join("", "/")).isEqualTo("/")
+ assertThat(PathUtils.join("/", "/")).isEqualTo("/")
+ assertThat(PathUtils.join("foo", "bar")).isEqualTo("foo/bar")
+ assertThat(PathUtils.join("foo/", "bar")).isEqualTo("foo/bar")
+ assertThat(PathUtils.join("foo", "/bar")).isEqualTo("/bar")
+ assertThat(PathUtils.join("/foo", "/bar")).isEqualTo("/bar")
+ assertThat(PathUtils.join("/foo", "bar/")).isEqualTo("/foo/bar/")
+ assertThat(PathUtils.join("/foo/", "bar/")).isEqualTo("/foo/bar/")
+ }
+
+ @Test
+ fun resolve() {
+ assertThat(PathUtils.resolve("")).isEmpty()
+ assertThat(PathUtils.resolve("/")).isEqualTo("/")
+ assertThat(PathUtils.resolve("../foo")).isEqualTo("foo")
+ assertThat(PathUtils.resolve("/../foo")).isEqualTo("/foo")
+ assertThat(PathUtils.resolve("foo/../bar")).isEqualTo("bar")
+ assertThat(PathUtils.resolve("/foo/../bar")).isEqualTo("/bar")
+ assertThat(PathUtils.resolve("foo/./bar")).isEqualTo("foo/bar")
+ assertThat(PathUtils.resolve("/foo/./bar")).isEqualTo("/foo/bar")
+ assertThat(PathUtils.resolve("foo/../../../bar/")).isEqualTo("bar")
+ assertThat(PathUtils.resolve("/foo/../../../bar/")).isEqualTo("/bar")
+ }
+}