summaryrefslogtreecommitdiff
path: root/libs/samba
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-08-19 00:34:03 +0200
committerJoel Klinghed <the_jk@spawned.biz>2024-08-19 00:34:03 +0200
commitd372cdcea3b3a0ba4b49180695c4e6b0e2d074a5 (patch)
treef3442d2a56afd362d172cef096c878a5e7311066 /libs/samba
parentb0d90f32974f6473552d8b1bf5387f9fc4995970 (diff)
Increase the samba implemetation
With the exception of openDir, largely untested.
Diffstat (limited to 'libs/samba')
-rw-r--r--libs/samba/src/main/cpp/jni.cpp10
-rw-r--r--libs/samba/src/main/cpp/jni.hpp43
-rw-r--r--libs/samba/src/main/cpp/samba.cpp128
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt68
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt25
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt79
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt40
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt57
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt2
-rw-r--r--libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt3
10 files changed, 408 insertions, 47 deletions
diff --git a/libs/samba/src/main/cpp/jni.cpp b/libs/samba/src/main/cpp/jni.cpp
index be13df7..30f03a9 100644
--- a/libs/samba/src/main/cpp/jni.cpp
+++ b/libs/samba/src/main/cpp/jni.cpp
@@ -97,4 +97,14 @@ LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str) {
return {env, env->NewStringUTF(str.c_str())};
}
+LocalRef<jobjectArray> CreateArray(JNIEnv* env, const Ref<jclass>& element_class, std::vector<LocalRef<jobject>> objects) {
+ auto ret = LocalRef<jobjectArray>(env, env->NewObjectArray(static_cast<jsize>(objects.size()), element_class.get(), nullptr));
+ ABORT_IF_NULL(env, ret);
+ jsize i = 0;
+ for (auto&& obj : objects) {
+ env->SetObjectArrayElement(ret.get(), i++, obj.release());
+ }
+ return ret;
+}
+
} // namespace jni
diff --git a/libs/samba/src/main/cpp/jni.hpp b/libs/samba/src/main/cpp/jni.hpp
index 90ca011..450fa71 100644
--- a/libs/samba/src/main/cpp/jni.hpp
+++ b/libs/samba/src/main/cpp/jni.hpp
@@ -3,6 +3,7 @@
#include <jni.h>
#include <string>
+#include <vector>
#define ABORT_IF_NOT_OK(x) (::jni::internal::_abort_if_not_ok(__FILE__, __LINE__, (x)))
#define ABORT_IF_NULL(env, x) (::jni::internal::_abort_if_null(__FILE__, __LINE__, (env), (x)))
@@ -21,8 +22,8 @@ class Ref {
public:
constexpr Ref() : env_(nullptr), ptr_(0) {}
Ref(const Ref<T>&) = delete;
- Ref<T>& operator=(const Ref<T>&) = delete;
+ [[nodiscard]] JNIEnv* env() const { return env_; }
[[nodiscard]] T get() const { return ptr_; }
[[nodiscard]] T release() {
auto ret = release_to_local();
@@ -44,7 +45,7 @@ class Ref {
virtual T release_to_local() = 0;
virtual void del() = 0;
- JNIEnv* const env_;
+ JNIEnv* env_;
T ptr_;
};
@@ -54,6 +55,10 @@ class LocalRef : public Ref<T> {
LocalRef(JNIEnv* env, T ptr): Ref<T>(env, ptr) {}
~LocalRef() override { free(); }
+ LocalRef(LocalRef<T> &&ref) noexcept : Ref<T>(ref.env_, ref.release()) {}
+
+ LocalRef& operator=(const Ref<T>& other) = delete;
+
protected:
T release_to_local() override { return this->ptr_; }
void del() override { free(); }
@@ -71,6 +76,8 @@ class ParamRef : public Ref<T> {
ParamRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr) {}
~ParamRef() override = default;
+ ParamRef& operator=(const Ref<T>& other) = delete;
+
protected:
T release_to_local() override {
if (this->ptr_)
@@ -83,11 +90,18 @@ class ParamRef : public Ref<T> {
template<class T>
class GlobalRef : public Ref<T> {
public:
- GlobalRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr ? env->NewGlobalRef(ptr) : 0) {}
- explicit GlobalRef(const Ref<T>& other) : Ref<T>(other.env_, other ? other.env_->NewGlobalRef(other.ptr_) : 0) {}
+ GlobalRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr ? static_cast<T>(env->NewGlobalRef(static_cast<jobject>(ptr))) : 0) {}
+ explicit GlobalRef(const Ref<T>& other) : Ref<T>(other.env(), other ? static_cast<T>(other.env()->NewGlobalRef(static_cast<jobject>(other.get()))) : 0) {}
~GlobalRef() override { free(); }
+ GlobalRef& operator=(const Ref<T>& other) {
+ free();
+ this->env_ = other.env();
+ this->ptr_ = other ? static_cast<T>(this->env_->NewGlobalRef(static_cast<jobject>(other.get()))) : 0;
+ return *this;
+ }
+
protected:
T release_to_local() override {
if (this->ptr_) {
@@ -117,19 +131,32 @@ LocalRef<jclass> FindClass(JNIEnv *env, const char *name);
LocalRef<jthrowable> ExceptionOccurred(JNIEnv* env);
-template<typename Out, typename In>
-LocalRef<Out> CallObjectMethod(JNIEnv* env, const Ref<In>& object, jmethodID method) {
- return {env, static_cast<Out>(env->CallObjectMethod(object.get(), method))};
+template<typename Out, typename In, typename... Params>
+LocalRef<Out> CallObjectMethod(JNIEnv* env, const Ref<In>& object, jmethodID method, Params... params) {
+ return {env, static_cast<Out>(env->CallObjectMethod(object.get(), method, params...))};
+}
+
+template<typename Out, typename... Params>
+LocalRef<Out> CallStaticObjectMethod(JNIEnv* env, const Ref<jclass>& clazz, jmethodID method, Params... params) {
+ return {env, static_cast<Out>(env->CallStaticObjectMethod(clazz.get(), method, params...))};
}
std::string StringToUTF8(JNIEnv* env, const Ref<jstring>& str);
LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str);
+LocalRef<jobjectArray> CreateArray(JNIEnv* env, const Ref<jclass>& element_class, std::vector<LocalRef<jobject>> objects);
+
namespace internal {
template<typename T>
-void _abort_if_null(const char* file, int line, JNIEnv* env, const jni::Ref<T>& ref) {
+inline void _abort_if_null(const char* file, int line, JNIEnv* env, const jni::Ref<T>& ref) {
+ if (ref) [[likely]] return;
+
+ _abort_with_exception(file, line, env);
+}
+
+inline void _abort_if_null(const char* file, int line, JNIEnv* env, jmethodID ref) {
if (ref) [[likely]] return;
_abort_with_exception(file, line, env);
diff --git a/libs/samba/src/main/cpp/samba.cpp b/libs/samba/src/main/cpp/samba.cpp
index e3b689d..7b83eb4 100644
--- a/libs/samba/src/main/cpp/samba.cpp
+++ b/libs/samba/src/main/cpp/samba.cpp
@@ -11,6 +11,8 @@
namespace {
+jni::LocalRef<jobject> CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat);
+
class Dir {
public:
Dir(std::shared_ptr<smb2_context> context, smb2dir* dir) : context_(std::move(context)), dir_(dir) {
@@ -24,6 +26,20 @@ class Dir {
Dir(const Dir&) = delete;
Dir& operator=(const Dir&) = delete;
+ jni::LocalRef<jobjectArray> List(JNIEnv* env, const jni::Ref<jclass>& dir_entry_clazz) {
+ smb2_rewinddir(context_.get(), dir_);
+ std::vector<jni::LocalRef<jobject>> tmp;
+ while (auto* ent = smb2_readdir(context_.get(), dir_)) {
+ // Skip . and .. entries.
+ if (ent->name[0] == '.' && (ent->name[1] == '\0' || (ent->name[1] == '.' && ent->name[2] == '\0'))) continue;
+ auto obj = CreateDirEntry(env, ent->name, ent->st);
+ if (obj) {
+ tmp.push_back(std::move(obj));
+ }
+ }
+ return jni::CreateArray(env, dir_entry_clazz, std::move(tmp));
+ }
+
private:
std::shared_ptr<smb2_context> context_;
smb2dir* const dir_;
@@ -81,6 +97,50 @@ class Context {
return ptr ? std::make_unique<Dir>(context_, ptr) : nullptr;
}
+ [[nodiscard]] jni::LocalRef<jobject> Entry(JNIEnv* env, const std::string& path) {
+ struct smb2_stat_64 stat; // NOLINT(*-pro-type-member-init)
+ auto ret = smb2_stat(context_.get(), path.c_str(), &stat);
+ if (ret) return {env, nullptr};
+ auto slash = path.find_last_of('/');
+ return CreateDirEntry(env, slash == std::string::npos ? path : path.substr(slash + 1), stat);
+ }
+
+ bool MakeDir(const std::string& path) {
+ return smb2_mkdir(context_.get(), path.c_str()) == 0;
+ }
+
+ bool RemoveDir(const std::string& path) {
+ return smb2_rmdir(context_.get(), path.c_str()) == 0;
+ }
+
+ bool Unlink(const std::string& path) {
+ return smb2_unlink(context_.get(), path.c_str()) == 0;
+ }
+
+ std::optional<std::string> ReadLink(const std::string& path) {
+ // Good to start with a fairly small size as current implementation
+ // of smb2_readlink uses strncpy, which pads the whole unused buffer
+ // with zeros.
+ uint32_t bufsize = 256;
+ std::vector<char> buf;
+ while (true) {
+ buf.resize(bufsize);
+ auto ret = smb2_readlink(context_.get(), path.c_str(), buf.data(), bufsize);
+ if (ret != 0)
+ return std::nullopt;
+ // smb2_readlink uses strncpy, so if actual path was larger than bufsize
+ // there will be no terminating zero.
+ auto it = std::find(buf.begin(), buf.end(), '\0');
+ if (it != buf.end())
+ return std::string(buf.begin(), it);
+ const auto previous = bufsize;
+ bufsize *= 2;
+ // Check for bufsize (a uint32_t) overflow.
+ if (bufsize <= previous)
+ return std::nullopt;
+ }
+ }
+
private:
struct ContextDeleter {
void operator()(smb2_context* context) {
@@ -121,6 +181,29 @@ jlong nativeContextOpenDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
return reinterpret_cast<jlong>(reinterpret_cast<Context*>(ptr)->OpenDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))).release());
}
+jobject nativeContextEntry(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->Entry(env, jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))).release();
+}
+
+jboolean nativeContextMakeDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->MakeDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE;
+}
+
+jboolean nativeContextRemoveDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->RemoveDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE;
+}
+
+jboolean nativeContextUnlink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ return reinterpret_cast<Context*>(ptr)->Unlink(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE;
+}
+
+jstring nativeContextReadLink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) {
+ auto ret = reinterpret_cast<Context*>(ptr)->ReadLink(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path)));
+ if (ret.has_value())
+ return jni::UTF8ToString(env, ret.value()).release();
+ return nullptr;
+}
+
void nativeUrlDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
delete reinterpret_cast<Url*>(ptr);
}
@@ -133,9 +216,20 @@ void nativeDirDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
delete reinterpret_cast<Dir*>(ptr);
}
+jni::GlobalRef<jclass> g_DirEntryClass(nullptr, nullptr);
+
+jobjectArray nativeDirList(JNIEnv* env, jclass clazz, jlong ptr) {
+ return reinterpret_cast<Dir*>(ptr)->List(env, g_DirEntryClass).release();
+}
+
+jni::GlobalRef<jclass> g_NativeSambaClass(nullptr, nullptr);
+jmethodID g_CreateDirEntry;
+
void RegisterSamba(JNIEnv* env) {
auto clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba");
ABORT_IF_NULL(env, clazz);
+ auto dir_entry_clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba$DirEntry");
+ ABORT_IF_NULL(env, dir_entry_clazz);
static const JNINativeMethod methods[] = {
{ "nativeContextNew", "(I)J", reinterpret_cast<void*>(&nativeContextNew) },
{ "nativeContextDestroy", "(J)V", reinterpret_cast<void*>(&nativeContextDestroy) },
@@ -143,14 +237,48 @@ void RegisterSamba(JNIEnv* env) {
{ "nativeContextConnect", "(JJLjava/lang/String;Ljava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextConnect) },
{ "nativeContextGetError", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextGetError) },
{ "nativeContextOpenDir", "(JLjava/lang/String;)J", reinterpret_cast<void*>(&nativeContextOpenDir) },
+ { "nativeContextEntry", "(JLjava/lang/String;)Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast<void*>(&nativeContextEntry) },
+ { "nativeContextMakeDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextMakeDir) },
+ { "nativeContextRemoveDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextRemoveDir) },
+ { "nativeContextUnlink", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextUnlink) },
+ { "nativeContextReadLink", "(JLjava/lang/String;)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextReadLink) },
{ "nativeUrlDestroy", "(J)V", reinterpret_cast<void*>(&nativeUrlDestroy) },
{ "nativeUrlPath", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeUrlPath) },
{ "nativeDirDestroy", "(J)V", reinterpret_cast<void*>(&nativeDirDestroy) },
+ { "nativeDirList", "(J)[Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast<void*>(&nativeDirList) },
};
auto ret = env->RegisterNatives(clazz.get(), methods, sizeof(methods) / sizeof(methods[0]));
ABORT_IF_NOT_OK(ret);
+
+ g_CreateDirEntry = env->GetStaticMethodID(clazz.get(), "createDirEntry", "(Ljava/lang/String;IJJ)Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;");
+ ABORT_IF_NULL(env, g_CreateDirEntry);
+ g_NativeSambaClass = clazz;
+ g_DirEntryClass = dir_entry_clazz;
+}
+
+jni::LocalRef<jobject> CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat) {
+ auto j_name = jni::UTF8ToString(env, name);
+ // Kotlin size casts Long to ULong
+ auto size = static_cast<jlong>(stat.smb2_size);
+ auto last_modified = static_cast<jlong>(stat.smb2_mtime);
+ jint type;
+ switch (stat.smb2_type) {
+ case SMB2_TYPE_DIRECTORY:
+ type = 0;
+ break;
+ case SMB2_TYPE_FILE:
+ type = 1;
+ break;
+ case SMB2_TYPE_LINK:
+ type = 2;
+ break;
+ default:
+ return {env, nullptr};
+ }
+
+ return jni::CallStaticObjectMethod<jobject>(env, g_NativeSambaClass, g_CreateDirEntry, j_name.get(), type, size, last_modified);
}
} // namespace
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt
index e48268d..1863e22 100644
--- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt
@@ -4,6 +4,7 @@
package org.the_jk.cleversync.io.samba
import androidx.annotation.Keep
+import java.time.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@@ -21,14 +22,35 @@ internal object NativeSamba {
fun connect(url: Url, credentials: SambaCredentials): Boolean
fun lastError(): String
fun openDir(path: String): Dir?
+ fun entry(path: String): DirEntry?
+ fun makeDir(path: String): Boolean
+ fun removeDir(path: String): Boolean
+ fun unlink(path: String): Boolean
+ fun readLink(path: String): String?
}
interface Url : Object {
fun path(): String
}
+ enum class DirEntryType {
+ DIR,
+ FILE,
+ LINK,
+ }
+
+ @Keep
+ data class DirEntry(
+ val name: String,
+ val type: DirEntryType,
+ val size: ULong,
+ val lastModified: Instant,
+ )
+
interface Dir : Object {
val path: String
+
+ fun list(): Array<DirEntry>
}
private class NativeContext(private var ptr: Long): Context {
@@ -55,6 +77,26 @@ internal object NativeSamba {
val dir = nativeContextOpenDir(ptr, path)
return if (dir != 0L) NativeDir(path, dir) else null
}
+
+ override fun entry(path: String): DirEntry? {
+ return nativeContextEntry(ptr, path)
+ }
+
+ override fun makeDir(path: String): Boolean {
+ return nativeContextMakeDir(ptr, path)
+ }
+
+ override fun removeDir(path: String): Boolean {
+ return nativeContextRemoveDir(ptr, path)
+ }
+
+ override fun unlink(path: String): Boolean {
+ return nativeContextUnlink(ptr, path)
+ }
+
+ override fun readLink(path: String): String? {
+ return nativeContextReadLink(ptr, path)
+ }
}
private class NativeUrl(private var ptr: Long): Url {
@@ -75,21 +117,47 @@ internal object NativeSamba {
nativeDirDestroy(ptr)
ptr = 0L
}
+
+ override fun list(): Array<DirEntry> {
+ return nativeDirList(ptr)
+ }
}
init {
System.loadLibrary("samba")
}
+ @JvmStatic
+ @Keep
+ @Suppress("UnusedPrivateMember")
+ private fun createDirEntry(name: String, type: Int, size: Long, lastModified: Long) =
+ DirEntry(
+ name = name,
+ when (type) {
+ 0 -> DirEntryType.DIR
+ 1 -> DirEntryType.FILE
+ 2 -> DirEntryType.LINK
+ else -> throw IllegalArgumentException("Unknown type: $type")
+ },
+ size = size.toULong(),
+ lastModified = Instant.ofEpochMilli(lastModified),
+ )
+
private external fun nativeContextNew(timeoutSeconds: Int): Long
private external fun nativeContextDestroy(ptr: Long)
private external fun nativeContextParseUrl(ptr: Long, url: String): Long
private external fun nativeContextConnect(ptr: Long, url: Long, username: String?, password: String?): Boolean
private external fun nativeContextGetError(ptr: Long): String
private external fun nativeContextOpenDir(ptr: Long, path: String): Long
+ private external fun nativeContextEntry(ptr: Long, path: String): DirEntry?
+ private external fun nativeContextMakeDir(ptr: Long, path: String): Boolean
+ private external fun nativeContextRemoveDir(ptr: Long, path: String): Boolean
+ private external fun nativeContextUnlink(ptr: Long, path: String): Boolean
+ private external fun nativeContextReadLink(otr: Long, path: String): String?
private external fun nativeUrlDestroy(ptr: Long)
private external fun nativeUrlPath(ptr: Long): String
private external fun nativeDirDestroy(ptr: Long)
+ private external fun nativeDirList(ptr: Long): Array<DirEntry>
}
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 05cc831..04aff44 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
@@ -12,21 +12,26 @@ internal class SambaConnection(uri: String, credentials: SambaCredentials) {
fun openDir(path: String): NativeSamba.Dir? =
if (connected) context.openDir(join(url!!.path(), path)) else null
+ fun entry(path: String): NativeSamba.DirEntry? =
+ if (connected) context.entry(join(url!!.path(), path)) else null
+
+ fun makeDir(path: String): Boolean =
+ connected && context.makeDir(join(url!!.path(), path))
+
+ fun removeDir(path: String): Boolean =
+ connected && context.removeDir(join(url!!.path(), path))
+
+ fun unlink(path: String): Boolean =
+ connected && context.unlink(join(url!!.path(), path))
+
+ fun readLink(path: String): String? =
+ if (connected) context.readLink(join(url!!.path(), path)) else null
+
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}"
}
-
- fun dirname(path: String): String {
- val last = path.lastIndexOf('/')
- return if (last == -1) path else path.substring(last + 1)
- }
-
- fun basename(path: String): String {
- val last = path.lastIndexOf('/')
- return if (last < 1) "" else path.substring(0, last - 1)
- }
}
}
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 b82e745..d9ec6aa 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
@@ -3,30 +3,61 @@ package org.the_jk.cleversync.io.samba
import androidx.lifecycle.LiveData
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.ModifiableDirectory
import org.the_jk.cleversync.io.ModifiableFile
import org.the_jk.cleversync.io.ModifiableLink
+import java.io.IOException
+import java.time.Instant
internal open class SambaDirectory(
private val conn: SambaConnection,
- private val dir: NativeSamba.Dir,
+ private val path: String,
+ override val name: String,
) : ModifiableDirectory {
override fun modifiableOpenDir(name: String): ModifiableDirectory? {
- val dir = conn.openDir(SambaConnection.join(dir.path, name)) ?: return null
- return SambaDirectory(conn, dir)
+ val newPath = SambaConnection.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? {
- TODO("Not yet implemented")
+ val newPath = SambaConnection.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)
}
override fun modifiableOpenLink(name: String): ModifiableLink? {
- TODO("Not yet implemented")
+ val newPath = SambaConnection.join(path, name)
+ val entry = conn.entry(newPath) ?: return null
+ if (entry.type != NativeSamba.DirEntryType.LINK) return null
+ return SambaLink(conn, newPath, name)
}
override fun modifiableList(): ModifiableDirectory.Content {
- TODO("Not yet implemented")
+ val directories = mutableListOf<ModifiableDirectory>()
+ val files = mutableListOf<ModifiableFile>()
+ val links = mutableListOf<ModifiableLink>()
+ val dir = conn.openDir(path)
+ if (dir != null) {
+ dir.list().forEach { entry ->
+ val entryPath = SambaConnection.join(path, entry.name)
+ when (entry.type) {
+ NativeSamba.DirEntryType.DIR -> {
+ directories.add(SambaDirectory(conn, entryPath, entry.name))
+ }
+ NativeSamba.DirEntryType.FILE -> {
+ files.add(SambaFile(conn, entryPath, entry.name, entry.size, entry.lastModified))
+ }
+ NativeSamba.DirEntryType.LINK -> {
+ links.add(SambaLink(conn, entryPath, entry.name))
+ }
+ }
+ }
+ dir.destroy()
+ }
+ return ModifiableDirectory.Content(directories, files, links)
}
override fun modifiableLiveList(): LiveData<ModifiableDirectory.Content> {
@@ -34,52 +65,48 @@ internal open class SambaDirectory(
}
override fun createDirectory(name: String): ModifiableDirectory {
- TODO("Not yet implemented")
+ val newPath = SambaConnection.join(path, name)
+ if (!conn.makeDir(newPath)) throw IOException(conn.error)
+ return SambaDirectory(conn, newPath, name)
}
override fun createFile(name: String): ModifiableFile {
- TODO("Not yet implemented")
+ val newPath = SambaConnection.join(path, name)
+ return SambaFile(conn, newPath, name, 0UL, Instant.EPOCH, Instant.EPOCH)
}
override fun createLink(name: String, target: Directory): ModifiableLink {
- TODO("Not yet implemented")
+ throw IOException("Unsupported operation")
}
override fun createLink(name: String, target: File): ModifiableLink {
- TODO("Not yet implemented")
+ throw IOException("Unsupported operation")
}
override fun createLink(name: String, target: String): ModifiableLink {
- TODO("Not yet implemented")
+ throw IOException("Unsupported operation")
}
override fun removeDirectory(name: String): Boolean {
- TODO("Not yet implemented")
+ return conn.removeDir(SambaConnection.join(path, name))
}
override fun removeFile(name: String): Boolean {
- TODO("Not yet implemented")
+ return conn.unlink(SambaConnection.join(path, name))
}
override fun removeLink(name: String): Boolean {
- TODO("Not yet implemented")
+ return conn.unlink(SambaConnection.join(path, name))
}
- override val name: String
- get() = SambaConnection.dirname(dir.path)
-
override fun openDir(name: String) = modifiableOpenDir(name)
- override fun openFile(name: String): File? {
- TODO("Not yet implemented")
- }
+ override fun openFile(name: String) = modifiableOpenFile(name)
- override fun openLink(name: String): Link? {
- TODO("Not yet implemented")
- }
+ override fun openLink(name: String) = modifiableOpenLink(name)
- override fun list(): Directory.Content {
- TODO("Not yet implemented")
+ override fun list() = with(modifiableList()) {
+ Directory.Content(directories, files, links)
}
override fun liveList(): LiveData<Directory.Content> {
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt
new file mode 100644
index 0000000..817c1bf
--- /dev/null
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt
@@ -0,0 +1,40 @@
+package org.the_jk.cleversync.io.samba
+
+import org.the_jk.cleversync.io.ModifiableFile
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.time.Instant
+
+internal class SambaFile(
+ private val conn: SambaConnection,
+ private val path: String,
+ override val name: String,
+ private val cachedSize: ULong,
+ private val cachedLastModified: Instant,
+ private var cacheEndOfLife: Instant = Instant.now().plusSeconds(60),
+) : ModifiableFile {
+ override fun write(): OutputStream {
+ TODO("Not yet implemented")
+ }
+
+ override fun read(): InputStream {
+ TODO("Not yet implemented")
+ }
+
+ override val size: ULong get() {
+ if (useCached()) return cachedSize
+ val entry = conn.entry(path) ?: throw IOException(conn.error)
+ return entry.size
+ }
+
+ override val lastModified: Instant get() {
+ if (useCached()) return cachedLastModified
+ val entry = conn.entry(path) ?: throw IOException(conn.error)
+ return entry.lastModified
+ }
+
+ private fun useCached(): Boolean {
+ return Instant.now().isBefore(cacheEndOfLife)
+ }
+}
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt
new file mode 100644
index 0000000..9d9fd1d
--- /dev/null
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt
@@ -0,0 +1,57 @@
+package org.the_jk.cleversync.io.samba
+
+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.io.IOException
+
+internal class SambaLink(
+ private val conn: SambaConnection,
+ private val path: String,
+ override val name: String,
+) : ModifiableLink {
+ override fun modifiableResolve(): ModifiableLink.ModifiableLinkTarget {
+ val (newPath, entry) = doResolve()
+ if (entry == null) return ModifiableLink.NoTarget
+ return when (entry.type) {
+ NativeSamba.DirEntryType.DIR -> ModifiableLink.ModifiableDirectoryTarget(SambaDirectory(conn, newPath, entry.name))
+ NativeSamba.DirEntryType.FILE -> ModifiableLink.ModifiableFileTarget(SambaFile(conn, newPath, entry.name, entry.size, entry.lastModified))
+ NativeSamba.DirEntryType.LINK -> ModifiableLink.NoTarget
+ }
+ }
+
+ override fun target(directory: Directory) {
+ throw IOException("Unsupported operation")
+ }
+
+ override fun target(file: File) {
+ throw IOException("Unsupported operation")
+ }
+
+ override fun target(name: String) {
+ throw IOException("Unsupported operation")
+ }
+
+ override fun resolve(): Link.LinkTarget {
+ val (newPath, entry) = doResolve()
+ if (entry == null) return Link.NoTarget
+ return when (entry.type) {
+ NativeSamba.DirEntryType.DIR -> Link.DirectoryTarget(SambaDirectory(conn, newPath, entry.name))
+ NativeSamba.DirEntryType.FILE -> Link.FileTarget(SambaFile(conn, newPath, entry.name, entry.size, entry.lastModified))
+ NativeSamba.DirEntryType.LINK -> Link.NoTarget
+ }
+ }
+
+ private fun doResolve(): Pair<String, NativeSamba.DirEntry?> {
+ var linkPath = path
+ var entry: NativeSamba.DirEntry? = null
+ while (true) {
+ val target = conn.readLink(linkPath) ?: break
+ linkPath = SambaConnection.join(linkPath, target)
+ entry = conn.entry(linkPath) ?: break
+ if (entry.type != NativeSamba.DirEntryType.LINK) break
+ }
+ return linkPath to entry
+ }
+}
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt
index 8b4a86d..762a61f 100644
--- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt
@@ -3,7 +3,7 @@ package org.the_jk.cleversync.io.samba
import android.content.res.Resources
import org.the_jk.cleversync.io.ModifiableTree
-internal class SambaTree(conn: SambaConnection, root: NativeSamba.Dir) : SambaDirectory(conn, root), ModifiableTree {
+internal class SambaTree(conn: SambaConnection, root: String) : SambaDirectory(conn, root, ""), ModifiableTree {
override fun description(resources: Resources): CharSequence {
TODO("Not yet implemented")
}
diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt b/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt
index 5c9bc4f..ebf47d2 100644
--- a/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt
+++ b/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt
@@ -12,7 +12,6 @@ object SambaTreeFactory {
fun modifiableTree(uri: String, credentials: SambaCredentials): Result<ModifiableTree> {
val connection = SambaConnection(uri, credentials)
if (!connection.connected) return Result.failure(Exception(connection.error))
- val root = connection.openDir("") ?: return Result.failure(Exception(connection.error))
- return Result.success(SambaTree(connection, root))
+ return Result.success(SambaTree(connection, ""))
}
}