diff options
Diffstat (limited to 'libs')
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") + } +} |
